function mxGraphMlCodec() {
    this.cachedRefObj = {};
};


mxGraphMlCodec.prototype.refRegexp = /^\{y\:GraphMLReference\s+(\d+)\}$/;
mxGraphMlCodec.prototype.staticRegexp = /^\{x\:Static\s+(.+)\.(.+)\}$/;

mxGraphMlCodec.prototype.decode = function (xml, callback, onError) {
    try {
        var doc = mxUtils.parseXml(xml);

        var graphs = this.getDirectChildNamedElements(doc.documentElement, mxGraphMlConstants.GRAPH);

        this.initializeKeys(doc.documentElement);

        var mxFile = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><mxfile>";
        for (var i = 0; i < graphs.length; i++) {
            var pageElement = graphs[i];

            var graph = this.createMxGraph();
            var model = graph.getModel();

            model.beginUpdate();
            try {
                this.nodesMap = {};
                this.edges = [];
                this.importGraph(pageElement, graph, graph.getDefaultParent());

                for (var i = 0; i < this.edges.length; i++) {
                    var edgesObj = this.edges[i];
                    var edges = edgesObj.edges;
                    var parent = edgesObj.parent;
                    var dx = edgesObj.dx, dy = edgesObj.dy;

                    for (var j = 0; j < edges.length; j++) {
                        this.importEdge(edges[j], graph, parent, dx, dy);
                    }
                }
            } catch (e) {
                console.log(e);
                throw e;
            } finally {
                model.endUpdate();
            }

            //update edges' labels to convert their labels relative coordinate to ours
            model.beginUpdate();
            try {
                var cells = graph.getModel().cells;
                var tr = graph.view.translate;

                for (var id in cells) {
                    var edge = cells[id];

                    if (edge.edge && edge.getChildCount() > 0) {
                        for (var i = 0; i < edge.getChildCount(); i++) {
                            var cell = edge.children[i];
                            var geo = cell.geometry;

                            if (!geo.adjustIt) continue;

                            var state = graph.view.getState(edge);
                            var abdPs = state.absolutePoints;
                            var p0 = abdPs[0];
                            var pe = abdPs[abdPs.length - 1];

                            var ratio = geo.x;
                            var dist = geo.y;
                            var dx = pe.x - p0.x
                            var dy = pe.y - p0.y

                            var x = p0.x + ratio * dx;
                            var y = p0.y + ratio * dy;

                            var d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;

                            x -= dist * dy;
                            y += dist * dx;

                            var np = graph.view.getRelativePoint(state, x, y);
                            geo.x = np.x;
                            geo.y = np.y;
                        }
                    }
                }
            } catch (e) {
                console.log(e);
                throw e;
            } finally {
                model.endUpdate();
            }

            mxFile += this.processPage(graph, i + 1);
        }

        mxFile += "</mxfile>";

        if (callback) {
            callback(mxFile);
        }
    } catch (e) {
        if (onError) {
            onError(e);
        }
    }
};

mxGraphMlCodec.prototype.initializeKeys = function (graphmlElement) {
    var keys = this.getDirectChildNamedElements(graphmlElement, mxGraphMlConstants.KEY);

    this.nodesKeys = {};
    this.edgesKeys = {};
    this.portsKeys = {};
    this.sharedData = {};

    this.nodesKeys[mxGraphMlConstants.NODE_GEOMETRY] = {};
    this.nodesKeys[mxGraphMlConstants.USER_TAGS] = {};
    this.nodesKeys[mxGraphMlConstants.NODE_STYLE] = {};
    this.nodesKeys[mxGraphMlConstants.NODE_LABELS] = {};
    this.nodesKeys[mxGraphMlConstants.NODE_GRAPHICS] = {};
    this.edgesKeys[mxGraphMlConstants.EDGE_GEOMETRY] = {};
    this.edgesKeys[mxGraphMlConstants.EDGE_STYLE] = {};
    this.edgesKeys[mxGraphMlConstants.EDGE_LABELS] = {};
    this.portsKeys[mxGraphMlConstants.PORT_LOCATION_PARAMETER] = {};
    this.portsKeys[mxGraphMlConstants.PORT_STYLE] = {};
    this.portsKeys[mxGraphMlConstants.PORT_VIEW_STATE] = {};

    var sharedDataId;

    for (var i = 0; i < keys.length; i++) {
        var keyObj = this.dataElem2Obj(keys[i]);

        var id = keyObj[mxGraphMlConstants.ID];
        var _for = keyObj[mxGraphMlConstants.KEY_FOR];
        var attName = keyObj[mxGraphMlConstants.KEY_NAME];
        var yType = keyObj[mxGraphMlConstants.KEY_YTYPE];

        if (attName == mxGraphMlConstants.SHARED_DATA) sharedDataId = id;

        attName = attName ? attName : yType

        //TODO handle the defaults inside these keys
        switch (_for) {
            case mxGraphMlConstants.NODE:
                this.nodesKeys[attName] = {key: id, keyObj: keyObj};
                break;
            case mxGraphMlConstants.EDGE:
                this.edgesKeys[attName] = {key: id, keyObj: keyObj};
                break;
            case mxGraphMlConstants.PORT:
                this.portsKeys[attName] = {key: id, keyObj: keyObj};
                break;
            case mxGraphMlConstants.ALL:
                var obj = {key: id, keyObj: keyObj};
                this.nodesKeys[attName] = obj;
                this.edgesKeys[attName] = obj;
                this.portsKeys[attName] = obj;
                break;
        }
    }

    var data = this.getDirectChildNamedElements(graphmlElement, mxGraphMlConstants.DATA);

    for (var i = 0; i < data.length; i++) {
        var key = data[i].getAttribute(mxGraphMlConstants.KEY);

        if (key == sharedDataId) {
            var sharedData = this.getDirectChildNamedElements(data[i], mxGraphMlConstants.Y_SHARED_DATA);

            for (var j = 0; j < sharedData.length; j++) {
                var dataItems = this.getDirectChildElements(sharedData[j]);

                for (var k = 0; k < dataItems.length; k++) {
                    var dkey = dataItems[k].getAttribute(mxGraphMlConstants.X_KEY);
                    this.sharedData[dkey] = dataItems[k];
                }
            }
        } else {
            var resources = this.getDirectChildNamedElements(data[i], mxGraphMlConstants.Y_RESOURCES);

            for (var j = 0; j < resources.length; j++) {
                var dataItems = this.getDirectChildElements(resources[j]);

                for (var k = 0; k < dataItems.length; k++) {
                    var dkey = dataItems[k].getAttribute(mxGraphMlConstants.ID);
                    this.sharedData[dkey] = dataItems[k];
                }
            }
        }
    }
};

mxGraphMlCodec.prototype.parseAttributes = function (elem, obj) {
    var atts = elem.attributes;
    if (atts) {

        for (var i = 0; i < atts.length; i++) {
            var val = atts[i].nodeValue;
            var ref = this.refRegexp.exec(val);
            var staticMem = this.staticRegexp.exec(val);

            if (ref) {
                var key = ref[1];
                var subObj = this.cachedRefObj[key];

                //already cached
                if (!subObj) {
                    subObj = {};
                    subObj[this.sharedData[key].nodeName] = this.dataElem2Obj(this.sharedData[key]);
                    this.cachedRefObj[key] = subObj;
                }

                obj[atts[i].nodeName] = subObj;
            } else if (staticMem) {
                obj[atts[i].nodeName] = {};
                obj[atts[i].nodeName][staticMem[1]] = staticMem[2];
            } else {
                obj[atts[i].nodeName] = val;
            }
        }
    }
};

mxGraphMlCodec.prototype.dataElem2Obj = function (elem) {
    var ref = this.getDirectFirstChildNamedElements(elem, mxGraphMlConstants.GRAPHML_REFERENCE)
        || elem.getAttribute(mxGraphMlConstants.REFID);
    var refKey = null;
    var origElem = elem;
    var obj = {};

    if (ref) {
        var key = (typeof ref === "string") ? ref : ref.getAttribute(mxGraphMlConstants.RESOURCE_KEY);
        var cachedObj = this.cachedRefObj[key];

        //already cached
        if (cachedObj) {
            //parse all attributes to update the reference
            this.parseAttributes(elem, cachedObj);
            return cachedObj;
        }

        elem = this.sharedData[key];
        refKey = key;
    }

    //parse all attributes
    this.parseAttributes(elem, obj);

    for (var i = 0; i < elem.childNodes.length; i++) {
        var child = elem.childNodes[i];

        if (child.nodeType == 1) {
            var attName = child.nodeName;

            //Special types of node (x:List and x:Static)
            if (attName == mxGraphMlConstants.X_LIST) {
                var arr = [];
                var arrayElem = this.getDirectChildElements(child);

                for (var j = 0; j < arrayElem.length; j++) {
                    attName = arrayElem[j].nodeName;
                    arr.push(this.dataElem2Obj(arrayElem[j]));
                }

                obj[attName] = arr;
            } else if (attName == mxGraphMlConstants.X_STATIC) {
                var member = child.getAttribute(mxGraphMlConstants.MEMBER);
                var dotPos = member.lastIndexOf('.');
                obj[member.substr(0, dotPos)] = member.substr(dotPos + 1);
            } else {
                var dotPos = attName.lastIndexOf(".");

                if (dotPos > 0) {
                    attName = attName.substr(dotPos + 1);
                }

                if (obj[attName] != null) {
                    if (!(obj[attName] instanceof Array)) {
                        obj[attName] = [obj[attName]];
                    }

                    obj[attName].push(this.dataElem2Obj(child));
                } else {
                    obj[attName] = this.dataElem2Obj(child);
                }
            }
        } else if ((child.nodeType == 3 || child.nodeType == 4) && child.textContent.trim()) {
            obj["#text"] = child.textContent;
        }
    }

    //cache referenced objects
    if (refKey) {
        var tmpObj = {};
        //parse all attributes before following the reference
        this.parseAttributes(origElem, tmpObj);
        tmpObj[this.sharedData[refKey].nodeName] = obj;
        this.cachedRefObj[refKey] = tmpObj;
        return tmpObj;
    }

    return obj;
};

mxGraphMlCodec.prototype.mapArray = function (arr, mapping, map) {
    var obj = {};
    for (var k = 0; k < arr.length; k++) {
        if (arr[k].name) {
            obj[arr[k].name] = arr[k].value || arr[k];
        }
    }
    this.mapObject(obj, mapping, map);
}
//Use mapping information to fill the map based on obj content
//TODO yjs looks like they need special handling
mxGraphMlCodec.prototype.mapObject = function (obj, mapping, map) {
    //defaults can be overridden by actual values later
    if (mapping.defaults) {
        for (var key in mapping.defaults) {
            map[key] = mapping.defaults[key];
        }
    }

    for (var key in mapping) {
        var parts = key.split('.');

        var val = obj;

        for (var i = 0; i < parts.length; i++) {
            if (!val) break;

            val = val[parts[i]];
        }

        if (val == null && obj) //some vals doesn't need to be split
        {
            val = obj[key];
        }

        if (val != null) {
            var mappingObj = mapping[key];
            if (typeof val === "string") {
                if (typeof mappingObj === "string") {
                    map[mappingObj] = val.toLowerCase();
                } else if (typeof mappingObj === "object") {
                    var modVal = val.toLowerCase();
                    switch (mappingObj.mod) {
                        case "color": //mxGraph support alfa in colors in the standard format
                            if (val.indexOf("#") == 0 && val.length == 9) {
                                modVal = "#" + val.substr(3) + val.substr(1, 2);
                            } else if (val == "TRANSPARENT") {
                                modVal = "none";
                            }
                            break;
                        case "shape":
//							console.log(val.toLowerCase());
                            modVal = mxGraphMlShapesMap[val.toLowerCase()];
                            break;
                        case "bpmnOutline":
//							console.log(val.toLowerCase());
                            modVal = mxGraphMlShapesMap.bpmnOutline[val.toLowerCase()];
                            break;
                        case "bpmnSymbol":
//							console.log(val.toLowerCase());
                            modVal = mxGraphMlShapesMap.bpmnSymbol[val.toLowerCase()];
                            break;
                        case "bool":
                            modVal = val == "true" ? "1" : "0";
                            break;
                        case "scale":
                            try {
                                modVal = parseFloat(val) * mappingObj.scale;
                            } catch (e) {
                                //nothing!
                            }
                            break;
                        case "arrow":
                            modVal = mxGraphMlArrowsMap[val];
                            break;
                    }
                    if (modVal != null)
                        map[mappingObj.key] = modVal;
                } else {
                    mappingObj(val, map);
                }
            } else if (val instanceof Array) {
                this.mapArray(val, mappingObj, map);
            } else if (val.name != null && val.value != null) //this is the case when a single y:Property is used
            {
                this.mapArray([val], mappingObj, map);
            } else {
                this.mapObject(val, mappingObj, map);
            }
        }
    }
};

mxGraphMlCodec.prototype.createMxGraph = function () {
    var graph = new mxGraph();
//    graph.setExtendParents(false);
//    graph.setExtendParentsOnAdd(false);
//    graph.setConstrainChildren(false);
//    graph.setHtmlLabels(true);
//    graph.getModel().maintainEdgeParent = false;
    return graph;
}

mxGraphMlCodec.prototype.importGraph = function (pageElement, graph, parent) {
    var nodes = this.getDirectChildNamedElements(pageElement, mxGraphMlConstants.NODE);

    var p = parent;
    var dx = 0, dy = 0;
    while (p && p.geometry) {
        dx += p.geometry.x;
        dy += p.geometry.y;

        p = p.parent;
    }

    for (var i = 0; i < nodes.length; i++) {
        this.importNode(nodes[i], graph, parent, dx, dy);
    }

    this.edges.push({
        edges: this.getDirectChildNamedElements(pageElement, mxGraphMlConstants.EDGE),
        parent: parent,
        dx: dx,
        dy: dy
    });
};

//FIXME port 0.5, 0.5 push the edge to the other side (bpmn example)
mxGraphMlCodec.prototype.importPort = function (portElement, portsMap) {
    var name = portElement.getAttribute(mxGraphMlConstants.PORT_NAME);
    var portObj = {};

    var data = this.getDirectChildNamedElements(portElement, mxGraphMlConstants.DATA);

    for (var i = 0; i < data.length; i++) {
        var d = data[i];
        var key = d.getAttribute(mxGraphMlConstants.KEY);

        var dataObj = this.dataElem2Obj(d);
//		console.log(dataObj);
        if (dataObj.key == this.portsKeys[mxGraphMlConstants.PORT_LOCATION_PARAMETER].key) {
            this.mapObject(dataObj, {
                "y:FreeNodePortLocationModelParameter.Ratio": function (val, map) {
                    var parts = val.split(',');
                    map["pos"] = {x: parts[0], y: parts[1]};
                }
            }, portObj);
        }
        /*
        else if (dataObj.key == this.portsKeys[mxGraphMlConstants.PORT_STYLE].key)
        {

        }
        else if (dataObj.key == this.portsKeys[mxGraphMlConstants.PORT_VIEW_STATE].key)
        {

        }*/
    }

    portsMap[name] = portObj;
};

mxGraphMlCodec.prototype.styleMap2Str = function (styleMap) {
    var semi = "";
    var str = "";

    for (var key in styleMap) {
        str += semi + key + "=" + styleMap[key];
        semi = ";";
    }

    return str;
};

mxGraphMlCodec.prototype.importNode = function (nodeElement, graph, parent, dx, dy) {
    var data = this.getDirectChildNamedElements(nodeElement, mxGraphMlConstants.DATA);
    var v;
    var id = nodeElement.getAttribute(mxGraphMlConstants.ID);

    var node = new mxCell();
    node.vertex = true;
    node.geometry = new mxGeometry(0, 0, 30, 30); //some node has no geometry, this is the default

    graph.addCell(node, parent);

    var style = {graphMlID: id};
    var mlStyleObj = null;
    var mlTemplate = null;
    var mlUserTags = null;
    var lblObj = null;
    var lbls = null;

    for (var i = 0; i < data.length; i++) {
        var d = data[i];
        var dataObj = this.dataElem2Obj(d);

        if (dataObj.key) {
            if (dataObj.key == this.nodesKeys[mxGraphMlConstants.NODE_GEOMETRY].key) {
                this.addNodeGeo(node, dataObj, dx, dy);
            } else if (dataObj.key == this.nodesKeys[mxGraphMlConstants.USER_TAGS].key) {
                mlUserTags = dataObj;
            } else if (dataObj.key == this.nodesKeys[mxGraphMlConstants.NODE_STYLE].key) {
                //			console.log(JSON.stringify(dataObj));
                mlStyleObj = dataObj;
                if (dataObj["yjs:StringTemplateNodeStyle"]) {
                    mlTemplate = dataObj["yjs:StringTemplateNodeStyle"]["#text"];
                } else {
                    this.addNodeStyle(node, dataObj, style);
                }
            } else if (dataObj.key == this.nodesKeys[mxGraphMlConstants.NODE_LABELS].key) {
                lblObj = dataObj;
            } else if (dataObj.key == this.nodesKeys[mxGraphMlConstants.NODE_GRAPHICS].key) {
                var shape = null, key = null;
                for (var key in dataObj) {
                    if (key == "key" || key == "#text") continue;

                    //Special case when a node has multiple graphics
                    //TODO support the open/closed states
                    if (key == "y:ProxyAutoBoundsNode") {
                        var realizers = dataObj[key]["y:Realizers"];
                        if (realizers) {
                            for (var key2 in realizers) {
                                if (key2 == "active" || key2 == "#text") continue;

                                shape = realizers[key2][realizers["active"]];
                                dataObj = {};
                                dataObj[key2] = shape;
                                break;
                            }
                        }
                    } else {
                        shape = dataObj[key];
                    }
                    break;
                }
                if (shape) {
                    if (shape[mxGraphMlConstants.GEOMETRY]) {
                        this.addNodeGeo(node, shape[mxGraphMlConstants.GEOMETRY], dx, dy);
                    }

                    if (shape[mxGraphMlConstants.NODE_LABEL]) {
                        lblObj = shape[mxGraphMlConstants.NODE_LABEL];
                    }
                }
                mlStyleObj = dataObj;
                this.addNodeStyle(node, dataObj, style);
            }
        }
    }

    var ports = this.getDirectChildNamedElements(nodeElement, mxGraphMlConstants.PORT);
    var portsMap = {};

    for (var i = 0; i < ports.length; i++) {
        this.importPort(ports[i], portsMap);
    }

    if (mlTemplate) {
        this.handleTemplates(mlTemplate, mlUserTags, node, style);
    }

    this.handleFixedRatio(node, style);

    //handle special compound shapes
    this.handleCompoundShape(node, style, mlStyleObj, lbls);

    //fix for stroke size of zero
    if (style["strokeWidth"] == 0) {
        style["strokeColor"] = "none";
    }

    node.style = this.styleMap2Str(style);

    var subGraphs = this.getDirectChildNamedElements(nodeElement, mxGraphMlConstants.GRAPH);

    for (var i = 0; i < subGraphs.length; i++) {
        this.importGraph(subGraphs[i], graph, node, portsMap);
    }

    //handle labels after node geometry is determined. It is also the last such that labels are on top
    if (lblObj)
        lbls = this.addLabels(node, lblObj, style, graph);

    this.nodesMap[id] = {node: node, ports: portsMap};
};

mxGraphMlCodec.prototype.addNodeStyle = function (node, dataObj, style) {
    //TODO move these static mapping objects outside such that they are defined once only
    var dashStyleFn = function (val, map) {
        if (val == "line") return;

        map["dashed"] = 1;
        //map["fixDash"] = 1;
        var pattern = null;
        switch (val) {
            case "DashDot":
                pattern = "3 1 1 1";
                break;
            case "Dot":
                pattern = "1 1";
                break;
            case "DashDotDot":
                pattern = "3 1 1 1 1 1";
                break;
            case "Dash":
                pattern = "3 1";
                break;
            case "dotted":
                pattern = "1 3";
                break;
            case "dashed":
                pattern = "5 2";
                break;
            default:
                pattern = val.replace(/0/g, '1');
        }

        if (pattern) {
            //Some patterns in graphML has only one number
            if (pattern.indexOf(" ") < 0) {
                pattern = pattern + " " + pattern;
            }
            map["dashPattern"] = pattern;
        }
    };

    var styleCommonMap =
        {
            "shape": {key: "shape", mod: "shape"},
            "y:Shape.type": {key: "shape", mod: "shape"},
            "configuration": {key: "shape", mod: "shape"},
            "type": {key: "shape", mod: "shape"},
            "assetName": {key: "shape", mod: "shape"},
            "activityType": {key: "shape", mod: "shape"},
            "fill": {key: "fillColor", mod: "color"},
            "fill.yjs:SolidColorFill.color": {key: "fillColor", mod: "color"},
            "fill.yjs:SolidColorFill.color.yjs:Color.value": {key: "fillColor", mod: "color"},
            "y:Fill": {
                "color": {key: "fillColor", mod: "color"},
                //"color2": {key: "gradientColor", mod: "color"}, //??
                "transparent": function (val, map) {
                    if (val == "true") {
                        map["fillColor"] = "none";
                    }
                }
            },
            "y:BorderStyle": {
                "color": {key: "strokeColor", mod: "color"},
                "width": "strokeWidth",
                "hasColor": function (val, map) {
                    if (val == "false") {
                        map["strokeColor"] = "none";
                    }
                },
                "type": dashStyleFn
                //"raised": ??
            },
            "stroke": {key: "strokeColor", mod: "color"},
            "stroke.yjs:Stroke":
                {
                    "dashStyle": dashStyleFn,
                    "dashStyle.yjs:DashStyle.dashes": dashStyleFn,
                    "fill": {key: "strokeColor", mod: "color"},
                    "fill.yjs:SolidColorFill.color": {key: "strokeColor", mod: "color"},
                    //"lineCap": "", //??
                    "thickness.sys:Double": "strokeWidth",
                    "thickness": "strokeWidth"
                }
        };

    var assetNodesStyle = mxUtils.clone(styleCommonMap);
    assetNodesStyle["defaults"] = {
        "fillColor": "#CCCCCC",
        "strokeColor": "#6881B3"
    };

    var bpmnActivityStyle = mxUtils.clone(styleCommonMap);
    bpmnActivityStyle["defaults"] = {
        "shape": "ext;rounded=1",
        "fillColor": "#FFFFFF",
        "strokeColor": "#000090"
    };

    var bpmnGatewayStyle = mxUtils.clone(styleCommonMap);
    bpmnGatewayStyle["defaults"] = {
        "shape": "rhombus;fillColor=#FFFFFF;strokeColor=#FFCD28"
    };

    var bpmnConversationStyle = mxUtils.clone(styleCommonMap);
    bpmnConversationStyle["defaults"] = {
        "shape": "hexagon",
        "strokeColor": "#007000"
    };

    var bpmnEventStyle = mxUtils.clone(styleCommonMap);
    bpmnEventStyle["defaults"] = {
        "shape": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=general",
        "outline": "standard"
    };
    bpmnEventStyle["characteristic"] = {key: "outline", mod: "bpmnOutline"};

    var bpmnDataObjectStyle = mxUtils.clone(styleCommonMap);
    bpmnDataObjectStyle["defaults"] = {
        "shape": "js:bpmnDataObject"
    };

    var bpmnDataStoreStyle = mxUtils.clone(styleCommonMap);
    bpmnDataStoreStyle["defaults"] = {
        "shape": "datastore"
    };

    var bpmnGroupNodeStyle = mxUtils.clone(styleCommonMap);
    bpmnGroupNodeStyle["defaults"] = {
        "shape": "swimlane;swimlaneLine=0;startSize=20;dashed=1;dashPattern=3 1 1 1;collapsible=0;rounded=1"
    };

    var bpmnChoreographyNodeStyle = mxUtils.clone(styleCommonMap);
    bpmnChoreographyNodeStyle["defaults"] = {
        "shape": "js:BpmnChoreography"//"swimlane;childLayout=stackLayout;horizontal=1;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;startSize=20;rounded=1;collapsible=0"
    };

    //approximation to GraphML shapes TODO improve them
    var bevelNodeStyle = mxUtils.clone(styleCommonMap);
    bevelNodeStyle["defaults"] = {
        "rounded": "1",
        "glass": "1",
        "strokeColor": "#FFFFFF"
    };
    bevelNodeStyle["inset"] = "strokeWidth";
    bevelNodeStyle["radius"] = "arcSize";
    bevelNodeStyle["drawShadow"] = {key: "shadow", mod: "bool"};
    bevelNodeStyle["color"] = {key: "fillColor", mod: "color", addGradient: "north"};
    bevelNodeStyle["color.yjs:Color.value"] = bevelNodeStyle["color"];

    var shinyPlateNodeStyle = mxUtils.clone(styleCommonMap);
    shinyPlateNodeStyle["defaults"] = {
        "rounded": "1",
        "arcSize": 10,
        "glass": "1",
        "shadow": "1",
        "strokeColor": "none"
        //,"rotation": -90 //TODO requires rotation!
    };
    shinyPlateNodeStyle["drawShadow"] = {key: "shadow", mod: "bool"};

    var demoGroupStyle = mxUtils.clone(styleCommonMap);
    demoGroupStyle["defaults"] = {
        "shape": "swimlane",
        "startSize": 20,
        "strokeWidth": 4,
        "spacingLeft": 10 //TODO can we change collapse icon to be in right side?
    };
    demoGroupStyle["isCollapsible"] = {key: "collapsible", mod: "bool"};
    demoGroupStyle["borderColor"] = {key: "strokeColor", mod: "color"};
    demoGroupStyle["folderFrontColor"] = {key: "fillColor", mod: "color"}; //TODO fillColor always match strokeColor!
//			demoGroupStyle["folderBackColor"] = {key:"fillColor", mod:"color"}; //??

    var collapsibleNodeStyle = mxUtils.clone(styleCommonMap);
    collapsibleNodeStyle["defaults"] = {
        "shape": "swimlane",
        "startSize": 20,
        "spacingLeft": 10 //TODO can we change collapse icon to be in right side?
    };
    collapsibleNodeStyle["yjs:PanelNodeStyle"] = {
        "color": {key: "swimlaneFillColor", mod: "color"},
        "color.yjs:Color.value": {key: "swimlaneFillColor", mod: "color"},
        "labelInsetsColor": {key: "fillColor", mod: "color"},
        "labelInsetsColor.yjs:Color.value": {key: "fillColor", mod: "color"}
    };

    var tableStyle = mxUtils.clone(styleCommonMap);
    tableStyle["defaults"] = {
        "shape": "js:table"
    };

    var imageNodeStyle = mxUtils.clone(styleCommonMap);
    imageNodeStyle["defaults"] = {
        "shape": "image"
    };

    imageNodeStyle["image"] = function (val, map) {
        map["image"] = val;
    };

    var svgNodeStyle = mxUtils.clone(styleCommonMap);
    svgNodeStyle["defaults"] = {
        "shape": "image"
    };
//	svgNodeStyle["y:SVGNodeProperties"] = {
//		"usingVisualBounds": ""//??
//	};
//	y:SVGModel.svgBoundsPolicy ??
    svgNodeStyle["y:SVGModel.y:SVGContent.y:Resource.#text"] = function (val, map) {
        map["image"] = "data:image/svg+xml," + ((window.btoa) ? btoa(val) : Base64.encode(val));
    };

    var groupNodeStyle = mxUtils.clone(styleCommonMap);
    groupNodeStyle["defaults"] = {
        "shape": "swimlane",
        "startSize": 20
    };

    groupNodeStyle["y:Shape.type"] = function (val, map) {
        if (val == "roundrectangle") {
            map['rounded'] = 1;
            map['arcSize'] = 5;
        }
    };

    var tableNodeStyle = mxUtils.clone(styleCommonMap);
    tableNodeStyle["defaults"] = {
        "shape": "js:table2"
    };

    var genericNodeStyle = mxUtils.clone(styleCommonMap);
    genericNodeStyle["defaults"] = {
        "gradientDirection": "east"
    };
    genericNodeStyle["y:Fill"]["color2"] = {key: "gradientColor", mod: "color"};
    genericNodeStyle["y:StyleProperties.y:Property"] = {
        "com.yworks.bpmn.characteristic": {key: "outline", mod: "bpmnOutline"},
        //TODO support colors for the icon itself other than the remaining shape!
//		"com.yworks.bpmn.icon.line.color": "",
        "com.yworks.bpmn.icon.fill": {key: "gradientColor", mod: "color"},
        "com.yworks.bpmn.icon.fill2": {key: "fillColor", mod: "color"},
        "com.yworks.bpmn.type": {key: "symbol", mod: "bpmnSymbol"},
        "y.view.ShadowNodePainter.SHADOW_PAINTING": {key: "shadow", mod: "bool"},
        "doubleBorder": {key: "double", mod: "bool"},
        "com.yworks.sbgn.style.radius": {key: "arcSize", mod: "scale", scale: 2},
        "com.yworks.sbgn.style.inverse": {key: "flipV", mod: "bool"}
    };
//	console.log(dataObj);
    this.mapObject(dataObj, {
        "yjs:ShapeNodeStyle": styleCommonMap,
        "demostyle:FlowchartNodeStyle": styleCommonMap,
        "demostyle:AssetNodeStyle": assetNodesStyle,
        "bpmn:ActivityNodeStyle": bpmnActivityStyle,
        "bpmn:GatewayNodeStyle": bpmnGatewayStyle,
        "bpmn:ConversationNodeStyle": bpmnConversationStyle,
        "bpmn:EventNodeStyle": bpmnEventStyle,
        "bpmn:DataObjectNodeStyle": bpmnDataObjectStyle,
        "bpmn:DataStoreNodeStyle": bpmnDataStoreStyle,
        "bpmn:GroupNodeStyle": bpmnGroupNodeStyle,
        "bpmn:ChoreographyNodeStyle": bpmnChoreographyNodeStyle,
        "yjs:BevelNodeStyle": bevelNodeStyle,
        "yjs:ShinyPlateNodeStyle": shinyPlateNodeStyle,
        "demostyle:DemoGroupStyle": demoGroupStyle,
        "yjs:CollapsibleNodeStyleDecorator": collapsibleNodeStyle,
        "bpmn:PoolNodeStyle": tableStyle,
        "yjs:TableNodeStyle": tableStyle,
        "demotablestyle:DemoTableStyle": tableStyle,
        "yjs:ImageNodeStyle": imageNodeStyle,
        //desktop
        "y:ShapeNode": styleCommonMap,
        "y:GenericNode": genericNodeStyle,
        "y:GenericGroupNode": genericNodeStyle,
        "y:TableNode": tableNodeStyle,
        "y:SVGNode": svgNodeStyle,
        "y:GroupNode": groupNodeStyle
    }, style);
};

mxGraphMlCodec.prototype.handleTemplates = function (template, userTags, node, styleMap) {
    if (template) {
        var w = node.geometry.width;
        var h = node.geometry.height;
        var header = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ' + w + ' ' + h + '"><g>';
        var footer = '</g></svg>';

        //TODO optimize this! probably only the text before defs has bindings
        var matches = null;
        var bindingPairs = [];
        //find template bindings
        var tempBindRegEx = /\{TemplateBinding\s+([^}]+)\}/g;

        while ((matches = tempBindRegEx.exec(template)) != null) {
            var replacement = "";
            switch (matches[1]) {
                case "width":
                    replacement = w;
                    break;
                case "height":
                    replacement = h;
                    break;
            }

            bindingPairs.push({match: matches[0], repl: replacement});
        }

        if (userTags && userTags["y:Json"]) {
            var json = JSON.parse(userTags["y:Json"]["#text"]);
            //find user tags bindings
            var userTagBindRegEx = /\{Binding\s+([^}]+)\}/g;

            while ((matches = userTagBindRegEx.exec(template)) != null) {
                var parts = matches[1].split(',');
                var val = json[parts[0]];

                if (val) {
                    if (parts.length > 1) {
                        if (parts[1].indexOf('Converter=')) {
                            var func = mxGraphMlConverters[parts[1].substr(11)]; //11 is the length of Converter=

                            if (func) {
                                var args = [val];

                                if (parts[2]) {
                                    args.push(parts[2].substr(11)); //11 is the length of Parameter=
                                }

                                val = func.apply(null, args)
                            }
                        }
                    }

                    bindingPairs.push({match: matches[0], repl: mxUtils.htmlEntities(val)});
                }
            }
        }

        for (var i = 0; i < bindingPairs.length; i++) {
            template = template.replace(bindingPairs[i].match, bindingPairs[i].repl);
        }

        bindingPairs = [];
        //Fix text elements (TODO can it be merged with the previous step?)
        var txtRegEx = /\<text.+data-content="([^"]+).+\<\/text\>/g;
        while ((matches = txtRegEx.exec(template)) != null) {
            var val = matches[0].substr(0, matches[0].length - 7) + matches[1] + "</text>"; //7 is the length of </text>
            bindingPairs.push({match: matches[0], repl: val});
        }

        for (var i = 0; i < bindingPairs.length; i++) {
            template = template.replace(bindingPairs[i].match, bindingPairs[i].repl);
        }

        var svg = header + template + footer;
        styleMap["shape"] = "image";
        styleMap["image"] = "data:image/svg+xml," + ((window.btoa) ? btoa(svg) : Base64.encode(svg));
    }
};

mxGraphMlCodec.prototype.handleCompoundShape = function (node, styleMap, mlStyleObj, lbls) {
    var shape = styleMap["shape"];

    if (shape && shape.indexOf("js:") == 0) {
        switch (shape) {
            case "js:bpmnArtifactShadow":
                styleMap["shadow"] = "1";
            case "js:bpmnArtifact":
                styleMap["shape"] = styleMap["symbol"];
                delete styleMap["fillColor"];
                delete styleMap["strokeColor"];
                delete styleMap["gradientColor"];
                this.handleCompoundShape(node, styleMap, mlStyleObj, lbls)
                break;
            case "js:bpmnDataObjectShadow":
            case "js:bpmnDataObject":
                styleMap["shape"] = "note;size=16";
//				console.log(mlStyleObj);
                mlStyleObj = mlStyleObj["bpmn:DataObjectNodeStyle"] || mlStyleObj["y:GenericNode"] || mlStyleObj["y:GenericGroupNode"];
                var tmpMap = {};

                this.mapObject(mlStyleObj, {
                    "y:StyleProperties.y:Property": {
                        "com.yworks.bpmn.dataObjectType": "dataObjectType",
                        "com.yworks.bpmn.marker1": "marker1"
                    }
                }, tmpMap);

                if (mlStyleObj["collection"] == "true" || tmpMap["marker1"] == "bpmn_marker_parallel") {
                    var cell2 = new mxCell('', new mxGeometry(0.5, 1, 10, 10), 'html=1;whiteSpace=wrap;shape=parallelMarker;');
                    cell2.vertex = true;
                    cell2.geometry.relative = true;
                    cell2.geometry.offset = new mxPoint(-5, -10);
                    node.insert(cell2);
                }
                if (mlStyleObj["type"] == "INPUT" || tmpMap["dataObjectType"] == "data_object_type_input") {
                    var cell1 = new mxCell('', new mxGeometry(0, 0, 10, 10), 'html=1;shape=singleArrow;arrowWidth=0.4;arrowSize=0.4;');
                    cell1.vertex = true;
                    cell1.geometry.relative = true;
                    cell1.geometry.offset = new mxPoint(2, 2);
                    node.insert(cell1);
                } else if (mlStyleObj["type"] == "OUTPUT" || tmpMap["dataObjectType"] == "data_object_type_output") {
                    var cell1 = new mxCell('', new mxGeometry(0, 0, 10, 10), 'html=1;shape=singleArrow;arrowWidth=0.4;arrowSize=0.4;fillColor=#000000;');
                    cell1.vertex = true;
                    cell1.geometry.relative = true;
                    cell1.geometry.offset = new mxPoint(2, 2);
                    node.insert(cell1);
                }
                break;
            case "js:BpmnChoreography":
                this.mapObject(mlStyleObj, {
                    "defaults": {
                        "shape": "swimlane;collapsible=0;rounded=1",
                        "startSize": "20",
                        "strokeColor": "#006000",
                        "fillColor": "#CCCCCC"
                    }
                }, styleMap);

                //TODO the shape should be clipped by parent borders. It should also be resized relative to its parent
                var pGeo = node.geometry;
                var cell1 = new mxCell('', new mxGeometry(0, pGeo.height - 20, pGeo.width, 20), 'strokeColor=#006000;fillColor=#777777;rounded=1');
                cell1.vertex = true;
                node.insert(cell1);

                //TODO handle labels accurately
                if (lbls && lbls.lblTxts) {
//					console.log(lbls);
                    node.value = lbls.lblTxts[0];
                    cell1.value = lbls.lblTxts[1];
                }
                break;
            case "js:bpmnActivityShadow":
            case "js:bpmnActivity":
                styleMap["shape"] = "ext;rounded=1";
                var tmpMap = {};
                mlStyleObj = mlStyleObj["y:GenericNode"] || mlStyleObj["y:GenericGroupNode"];

                this.mapObject(mlStyleObj, {
                    "y:StyleProperties.y:Property": {
                        "com.yworks.bpmn.taskType": "taskType",
                        "com.yworks.bpmn.activityType": "activityType",
                        "com.yworks.bpmn.marker1": "marker1",
                        "com.yworks.bpmn.marker2": "marker2",
                        "com.yworks.bpmn.marker3": "marker3",
                        "com.yworks.bpmn.marker4": "marker4"
                    }
                }, tmpMap);

                switch (tmpMap["activityType"]) {
                    case "activity_type_transaction":
                        styleMap["double"] = "1";
                        break;
                }

                switch (tmpMap["taskType"]) {
                    case "task_type_send":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 19, 12), 'shape=message;fillColor=#000000;strokeColor=#FFFFFF;');
                        item1.geometry.offset = new mxPoint(4, 7);
                        break;
                    case "task_type_receive":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 19, 12), 'shape=message;');
                        item1.geometry.offset = new mxPoint(4, 7);
                        break;
                    case "task_type_user":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 15, 15), 'shape=mxgraph.bpmn.user_task;');
                        item1.geometry.offset = new mxPoint(4, 5);
                        break;
                    case "task_type_manual":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 15, 10), 'shape=mxgraph.bpmn.manual_task;');
                        item1.geometry.offset = new mxPoint(4, 7);
                        break;
                    case "task_type_business_rule":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 18, 13), 'shape=mxgraph.bpmn.business_rule_task;');
                        item1.geometry.offset = new mxPoint(4, 7);
                        break;
                    case "task_type_service":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 15, 15), 'shape=mxgraph.bpmn.service_task;');
                        item1.geometry.offset = new mxPoint(4, 5);
                        break;
                    case "task_type_script":
                        var item1 = new mxCell('', new mxGeometry(0, 0, 15, 15), 'shape=mxgraph.bpmn.script_task;');
                        item1.geometry.offset = new mxPoint(4, 5);
                        break;
                }

                if (item1) {
                    item1.vertex = true;
                    item1.geometry.relative = true;
                    node.insert(item1);
                    item1 = null;
                }

                var numIcons = 0;

                for (var i = 1; i <= 4; i++) {
                    if (tmpMap["marker" + i])
                        numIcons++;
                }

                var iconX = -7.5 * numIcons - 2 * (numIcons - 1);

                for (var i = 1; i <= numIcons; i++) {
                    switch (tmpMap["marker" + i]) {
                        case "bpmn_marker_closed":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 15), 'shape=plus;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -20);
                            break;
                        case "bpmn_marker_open":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 15), 'shape=rect;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -20);
                            var item2 = new mxCell('', new mxGeometry(0.5, 0.5, 8, 1), 'shape=rect;part=1;');
                            item2.geometry.offset = new mxPoint(-4, -1);
                            item2.geometry.relative = true;
                            item2.vertex = true;
                            item1.insert(item2);
                            break;
                        case "bpmn_marker_loop":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 15), 'shape=mxgraph.bpmn.loop;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -20);
                            break;
                        case "bpmn_marker_parallel":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 15), 'shape=parallelMarker;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -20);
                            break;
                        case "bpmn_marker_sequential":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 15), 'shape=parallelMarker;direction=south;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -20);
                            break;
                        case "bpmn_marker_ad_hoc":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 10), 'shape=mxgraph.bpmn.ad_hoc;strokeColor=none;flipH=1;part=1;fillColor=#000000');
                            item1.geometry.offset = new mxPoint(iconX, -17);
                            break;
                        case "bpmn_marker_compensation":
                            var item1 = new mxCell('', new mxGeometry(0.5, 1, 15, 11), 'shape=mxgraph.bpmn.compensation;part=1;');
                            item1.geometry.offset = new mxPoint(iconX, -18);
                            break;
                    }
                    item1.geometry.relative = true;
                    item1.vertex = true;
                    node.insert(item1);
                    iconX += 20;
                }
                break;
            case "js:table":
                //TODO we need 2 passes to find the exact shift of columns/rows especially when there is rows inside rows
                //TODO Internal table strokes needs to match table strokeWidth and only be on one side
                //TODO code optimization
                styleMap["shape"] = "swimlane;collapsible=0;swimlaneLine=0";
                var tableObj = mlStyleObj["yjs:TableNodeStyle"] || mlStyleObj["demotablestyle:DemoTableStyle"];

                if (!tableObj && mlStyleObj["bpmn:PoolNodeStyle"]) {
                    tableObj = mlStyleObj["bpmn:PoolNodeStyle"]["yjs:TableNodeStyle"];
                }

//				console.log(tableObj);

                this.mapObject(tableObj, {
                    "backgroundStyle.demotablestyle:TableBackgroundStyle": {
                        "insetFill.yjs:SolidColorFill.color.yjs:Color.value": {key: "fillColor", mod: "color"},
                        "tableBackgroundFill.yjs:SolidColorFill.color.yjs:Color.value": {
                            key: "swimlaneFillColor",
                            mod: "color"
                        },
                        "tableBackgroundStroke.yjs:Stroke": {
                            "fill": {key: "strokeColor", mod: "color"},
                            "thickness": "strokeWidth"
                        }
                    },
                    "backgroundStyle.yjs:ShapeNodeStyle.fill": {key: "fillColor", mod: "color"},
                    "backgroundStyle.yjs:ShapeNodeStyle.fill.yjs:SolidColorFill.color": {key: "fillColor", mod: "color"}
                }, styleMap);

                //Lane fill color is the same as the fill color
                styleMap["swimlaneFillColor"] = styleMap["fillColor"];

                tableObj = tableObj["table"]["y:Table"];

                var x = 0, y = 0, xShift = {x: 0}, yShift = 0;
                var insets = tableObj["Insets"];

                if (insets) {
                    insets = insets.split(',');

                    if (insets[0] != "0") {
                        styleMap["startSize"] = insets[0];
                        xShift.x = parseFloat(insets[0]);
                        //x += xShift.x;
                        styleMap["horizontal"] = "0";
                    } else if (insets[1] != "0") {
                        styleMap["startSize"] = insets[1];
                        yShift = parseFloat(insets[1]);
                        y += yShift;
                    }
                } else {
                    styleMap["startSize"] = "0";
                }

                var defRowStyle = {};

                var rowMapping = {
                    "Insets": function (val, map) {
                        map["startSize"] = val.split(',')[0];
                    },
                    "Style.bpmn:AlternatingLeafStripeStyle": {
                        "evenLeafDescriptor.bpmn:StripeDescriptor": {
                            "insetFill": {key: "evenFill", mod: "color"},
                            "backgroundFill": {key: "evenLaneFill", mod: "color"}
                        },
                        "oddLeafDescriptor.bpmn:StripeDescriptor": {
                            "insetFill": {key: "oddFill", mod: "color"},
                            "backgroundFill": {key: "oddLaneFill", mod: "color"}
                        }
                        //parentDescriptor ??
                        //TODO collect common types in a special mapping hash
                    },
                    "Style.yjs:NodeStyleStripeStyleAdapter": {
                        "demotablestyle:DemoStripeStyle": {
                            "stripeInsetFill.yjs:SolidColorFill.color.yjs:Color.value": {
                                key: "fillColor",
                                mod: "color"
                            },
                            "tableLineFill.yjs:SolidColorFill.color.yjs:Color.value": {key: "strokeColor", mod: "color"}
                        },
                        "yjs:ShapeNodeStyle": {
                            "fill": {key: "swimlaneFillColor", mod: "color"}
                        }
                    },
                    "Size": "height"
                };
                this.mapObject(tableObj["RowDefaults"], {
                    "defaults": {
                        "shape": "swimlane;collapsible=0;horizontal=0",
                        "startSize": "0"
                    },
                    "y:StripeDefaults": rowMapping
                }, defRowStyle);

                var defColStyle = {};

                var colMapping = {
                    "Insets": function (val, map) {
                        map["startSize"] = val.split(',')[1];
                    },
                    "Style.bpmn:AlternatingLeafStripeStyle": {
                        "evenLeafDescriptor.bpmn:StripeDescriptor": {
                            "insetFill": {key: "evenFill", mod: "color"},
                            "backgroundFill": {key: "evenLaneFill", mod: "color"}
                        },
                        "oddLeafDescriptor.bpmn:StripeDescriptor": {
                            "insetFill": {key: "oddFill", mod: "color"},
                            "backgroundFill": {key: "oddLaneFill", mod: "color"}
                        }
                        //parentDescriptor ??
                        //TODO collect common types in a special mapping hash
                    },
                    "Style.yjs:NodeStyleStripeStyleAdapter": {
                        "demotablestyle:DemoStripeStyle": {
                            "stripeInsetFill.yjs:SolidColorFill.color.yjs:Color.value": {
                                key: "fillColor",
                                mod: "color"
                            },
                            "tableLineFill.yjs:SolidColorFill.color.yjs:Color.value": {key: "strokeColor", mod: "color"}
                        },
                        "yjs:ShapeNodeStyle": {
                            "fill": {key: "swimlaneFillColor", mod: "color"}
                        }
                    },
                    "Size": "width"
                };

                this.mapObject(tableObj["ColumnDefaults"], {
                    "defaults": {
                        "shape": "swimlane;collapsible=0",
                        "startSize": "0",
                        "fillColor": "none"
                    },
                    "y:StripeDefaults": colMapping
                }, defColStyle);

                var pGeo = node.geometry;

                var rows = tableObj["Rows"]["y:Row"];
                y += parseFloat(defColStyle["startSize"]);

                var maxX = xShift.x;
                var initX = xShift.x;
                xShift.lx = xShift.x;

                //TODO We need two passes to determine the header size!
                if (rows) {
                    if (!(rows instanceof Array))
                        rows = [rows];

                    for (var i = 0; i < rows.length; i++) {
                        xShift.x = initX;
                        xShift.lx = initX;
                        y = this.addRow(rows[i], node, (i & 1), y, xShift, rowMapping, defRowStyle);
                        maxX = Math.max(xShift.x, maxX);
                    }
                }

                var columns = tableObj["Columns"]["y:Column"];
                x = maxX;//parseFloat(defRowStyle["startSize"]);

                if (columns) {
                    if (!(columns instanceof Array))
                        columns = [columns];

                    for (var i = 0; i < columns.length; i++) {
                        x = this.addColumn(columns[i], node, (i & 1), x, yShift, colMapping, defColStyle);
                    }
                }

                break;
            case "js:table2":
                styleMap["shape"] = "swimlane;collapsible=0;swimlaneLine=0";
//				console.log(mlStyleObj);
                var tmpMap = {};
                this.mapObject(mlStyleObj, {
                    "y:TableNode": {
                        "y:StyleProperties.y:Property": {
                            "yed.table.section.color": {key: "secColor", mod: "color"},
                            "yed.table.header.height": "headerH",
                            "yed.table.header.color.main": {key: "headerColor", mod: "color"},
                            "yed.table.header.color.alternating": {key: "headerColorAlt", mod: "color"},
                            "yed.table.lane.color.main": {key: "laneColor", mod: "color"},
                            "yed.table.lane.color.alternating": {key: "laneColorAlt", mod: "color"},
                            "yed.table.lane.style": "laneStyle",
                            "com.yworks.bpmn.type": "isHorz",
                            "POOL_LANE_COLOR_ALTERNATING": {key: "laneColorAlt", mod: "color"},
                            "POOL_LANE_COLOR_MAIN": {key: "laneColor", mod: "color"},
                            "POOL_LANE_STYLE": "laneStyle",
                            "POOL_HEADER_COLOR_MAIN": {key: "headerColor", mod: "color"},
                            "POOL_HEADER_COLOR_ALTERNATING": {key: "headerColorAlt", mod: "color"},
                            "POOL_TABLE_SECTION_COLOR": {key: "secColor", mod: "color"}
//							//Not Used!
//							"y.view.tabular.TableNodePainter.ALTERNATE_ROW_STYLE": {
//								"y:SimpleStyle": {
//									"fillColor": {key: "rowAltFillColor", mod: "color"},
//									"lineColor": {key: "rowAltLineColor", mod: "color"},
//									"lineType": "rowAltlineType",
//									"lineWidth": "rowAltlineWidth"
//								}
//							},
//							"y.view.tabular.TableNodePainter.ALTERNATE_COLUMN_STYLE": {
//								"y:SimpleStyle": {
//									"fillColor": {key: "colAltFillColor", mod: "color"},
//									"lineColor": {key: "colAltLineColor", mod: "color"},
//									"lineType": "colAltlineType",
//									"lineWidth": "colAltlineWidth"
//								}
//							}
                        },
                        "y:Table": {
                            "y:DefaultColumnInsets.top": "colHHeight",
                            "y:DefaultRowInsets.left": "rowHWidth",
                            "y:Insets": {
                                "top": "tblHHeight",
                                "left": "tblHWidth"
                            }
                        }
                    }
                }, tmpMap);

                styleMap["swimlaneFillColor"] = styleMap["fillColor"];

                var isHor = tmpMap["isHorz"] == "pool_type_lane_and_column"
                    || tmpMap["isHorz"] == "pool_type_empty"
                    || tmpMap["isHorz"] == "pool_type_lane";

                var th = 0, tw = 0;
                if (isHor) {
                    tw = parseFloat(tmpMap["tblHWidth"]);
                } else {
                    th = parseFloat(tmpMap["tblHHeight"]);
                }

                styleMap["startSize"] = th ? th : tw;
                //Assumptions: There is always rows and cols in every table
                //Also all tables seems to be not rotated
                try {
                    var rows = mlStyleObj["y:TableNode"]["y:Table"]["y:Rows"]["y:Row"];
                    var cols = mlStyleObj["y:TableNode"]["y:Table"]["y:Columns"]["y:Column"];

                    var atts4Rows = tmpMap["laneStyle"] == "lane.style.rows" || tmpMap["laneStyle"] == "lane_style_rows";

                    if (!(rows instanceof Array))
                        rows = [rows];

                    if (!(cols instanceof Array))
                        cols = [cols];

                    var rowStartSize = parseFloat(tmpMap["rowHWidth"]);
                    for (var i = 0; i < rows.length; i++) {
                        if (rows[i]["y:Insets"])
                            rowStartSize = Math.max(rowStartSize,
                                parseFloat(rows[i]["y:Insets"]["left"]) + parseFloat(rows[i]["y:Insets"]["right"]));
                    }

                    var colStartSize = parseFloat(tmpMap["colHHeight"]);
                    for (var i = 0; i < cols.length; i++) {
                        if (cols[i]["y:Insets"])
                            colStartSize = Math.max(colStartSize,
                                parseFloat(cols[i]["y:Insets"]["top"]) + parseFloat(cols[i]["y:Insets"]["bottom"]));
                    }

                    if (atts4Rows) {
                        this.addTbl2Rows(node, rows, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap);
                        this.addTbl2Cols(node, cols, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap);
                    } else {
                        this.addTbl2Cols(node, cols, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap);
                        this.addTbl2Rows(node, rows, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap);
                    }
                } catch (e) {
                    //nothing!
                }
                break;
            case "js:relationship_big_entity":
                styleMap['shape'] = "swimlane;startSize=30;rounded=1;arcSize=5;collapsible=0";
                var fill = mlStyleObj["y:GenericNode"]["y:Fill"];

                if (fill) {
                    styleMap['fillColor'] = fill["color2"];
                    styleMap['swimlaneFillColor'] = fill["color"];
                }
                break;
            case "js:relationship_attribute":
                if (styleMap["double"] == "1") {
                    styleMap['shape'] = "doubleEllipse";
                } else {
                    styleMap['shape'] = "ellipse";
                }
                break;
        }

        if (shape.indexOf("Shadow") > 0) {
            styleMap["shadow"] = "1";
        }
    }
};

mxGraphMlCodec.prototype.addTbl2Rows = function (node, rows, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap) {
    var y = th + colStartSize;
    var isBPMN = tmpMap["isHorz"] != null;

    for (var i = 0; i < rows.length; i++) {
        var odd = i & 1;
        var cell = new mxCell();
        cell.vertex = true;
        var rowStyle = {
            "shape": "swimlane;collapsible=0;horizontal=0",
            "startSize": rowStartSize,
            "fillColor": tmpMap["secColor"] || "none",
            "swimlaneLine": (isBPMN ? "0" : "1")
        };

        if (parseFloat(rowStyle["startSize"]) == 0) {
            rowStyle["fillColor"] = "none";
            rowStyle["swimlaneLine"] = "0";
        }

        if (atts4Rows) {
            var fillColor = odd ? tmpMap["headerColorAlt"] : tmpMap["headerColor"];
            rowStyle["swimlaneFillColor"] = odd ? tmpMap["laneColorAlt"] : tmpMap["laneColor"];
            rowStyle["fillColor"] = fillColor ? fillColor : rowStyle["swimlaneFillColor"];
        }

        var height = parseFloat(rows[i]["height"]);
        var dh = (isBPMN && i == 0) ? colStartSize : 0;
        cell.geometry = new mxGeometry(tw, y - dh, node.geometry.width - tw, height + dh);
        y += height;

//		if (lblObj)
//			this.addLabels(cell, lblObj, rowStyle)

        cell.style = this.styleMap2Str(rowStyle);
        node.insert(cell);
    }
};

mxGraphMlCodec.prototype.addTbl2Cols = function (node, cols, th, tw, rowStartSize, colStartSize, atts4Rows, tmpMap) {
    var x = rowStartSize + tw;
    var isBPMN = tmpMap["isHorz"] != null;

    for (var i = 0; i < cols.length; i++) {
        var odd = i & 1;
        var cell = new mxCell();
        cell.vertex = true;
        var colStyle = {
            "shape": "swimlane;collapsible=0",
            "startSize": colStartSize,
            "fillColor": tmpMap["secColor"] || "none",
            "swimlaneLine": (isBPMN ? "0" : "1")
        };

        if (parseFloat(colStyle["startSize"]) == 0) {
            colStyle["fillColor"] = "none";
        }

        if (!atts4Rows) {
            var fillColor = odd ? tmpMap["headerColorAlt"] : tmpMap["headerColor"];
            colStyle["swimlaneFillColor"] = odd ? tmpMap["laneColorAlt"] : tmpMap["laneColor"];
            colStyle["fillColor"] = fillColor ? fillColor : colStyle["swimlaneFillColor"];
        }

        var width = parseFloat(cols[i]["width"]);
        var dw = (isBPMN && i == 0) ? rowStartSize : 0;
        cell.geometry = new mxGeometry(x - dw, th, width + dw, node.geometry.height - th);
        x += width;

//		if (lblObj)
//			this.addLabels(cell, lblObj, rowStyle)

        cell.style = this.styleMap2Str(colStyle);
        node.insert(cell);
    }
};

mxGraphMlCodec.prototype.addRow = function (row, parent, odd, y, xShift, rowMapping, defRowStyle) {
    var cell = new mxCell();
    cell.vertex = true;
    var rowStyle = mxUtils.clone(defRowStyle);
    this.mapObject(row, rowMapping, rowStyle);

    if (odd) {
        if (rowStyle["oddFill"])
            rowStyle["fillColor"] = rowStyle["oddFill"];

        if (rowStyle["oddLaneFill"])
            rowStyle["swimlaneFillColor"] = rowStyle["oddLaneFill"];
    } else {
        if (rowStyle["evenFill"])
            rowStyle["fillColor"] = rowStyle["evenFill"];

        if (rowStyle["evenLaneFill"])
            rowStyle["swimlaneFillColor"] = rowStyle["evenLaneFill"];
    }

    var height = parseFloat(rowStyle["height"]);
    cell.geometry = new mxGeometry(xShift.lx, y, parent.geometry.width - xShift.lx, height);

    var lblObj = row["Labels"];

    if (lblObj)
        this.addLabels(cell, lblObj, rowStyle)

    cell.style = this.styleMap2Str(rowStyle);
    parent.insert(cell);

    var subRow = row["y:Row"];

    xShift.lx = 0;
    if (rowStyle["startSize"]) {
        xShift.lx = parseFloat(rowStyle["startSize"]);
        xShift.x += xShift.lx;
    }

    var initX = xShift.x, maxX = xShift.x, initLx = xShift.lx;
    var subY = 0;
    if (subRow) {
        if (!(subRow instanceof Array))
            subRow = [subRow];

        for (var i = 0; i < subRow.length; i++) {
            xShift.x = initX;
            xShift.lx = initLx;
            subY = this.addRow(subRow[i], cell, (i & 1), subY, xShift, rowMapping, defRowStyle);
            maxX = Math.max(xShift.x, maxX)
        }
    }
    xShift.x = maxX;
    height = Math.max(height, subY);
    cell.geometry.height = height;
    y += height;

    return y;
}

mxGraphMlCodec.prototype.addColumn = function (column, parent, odd, x, yShift, colMapping, defColStyle) {
    var cell = new mxCell();
    cell.vertex = true;
    var colStyle = mxUtils.clone(defColStyle);
    this.mapObject(column, colMapping, colStyle);

    if (odd) {
        if (colStyle["oddFill"])
            colStyle["fillColor"] = colStyle["oddFill"];

        if (colStyle["oddLaneFill"])
            colStyle["swimlaneFillColor"] = colStyle["oddLaneFill"];
    } else {
        if (colStyle["evenFill"])
            colStyle["fillColor"] = colStyle["evenFill"];

        if (colStyle["evenLaneFill"])
            colStyle["swimlaneFillColor"] = colStyle["evenLaneFill"];
    }

    var width = parseFloat(colStyle["width"]);
    cell.geometry = new mxGeometry(x, yShift, width, parent.geometry.height - yShift);

    var lblObj = column["Labels"];

    if (lblObj)
        this.addLabels(cell, lblObj, colStyle)

    cell.style = this.styleMap2Str(colStyle);
    parent.insert(cell);

    var subCol = column["y:Column"];

    var subX = 0;
    if (subCol) {
        if (!(subCol instanceof Array))
            subCol = [subCol];

        for (var i = 0; i < subCol.length; i++) {
            subX = this.addColumn(subCol[i], cell, (i & 1), subX, yShift, colMapping, defColStyle);
        }
    }
    width = Math.max(width, subX);
    cell.geometry.width = width;
    x += width;
    return x;
}

mxGraphMlCodec.prototype.handleFixedRatio = function (node, styleMap) {
    var shape = styleMap["shape"];
    var geo = node.geometry;

    if (shape && geo) {
        if (shape.indexOf(";aspect=fixed") > 0) {

            var min = Math.min(geo.height, geo.width);

            if (min == geo.height) //fix coordinates
            {
                geo.x += (geo.width - min) / 2;
            }

            geo.height = min;
            geo.width = min;
        } else if (shape.indexOf(";rotation=90") > 0 || shape.indexOf(";rotation=-90") > 0) {
            var h = geo.height;
            var w = geo.width;
            geo.height = w;
            geo.width = h;
            var diff = (h - w) / 2;
            geo.x -= diff;
            geo.y += diff;
        }
    }
};

mxGraphMlCodec.prototype.addNodeGeo = function (node, geoObj, dx, dy) {
    var geoRect = geoObj[mxGraphMlConstants.RECT];

    var x = 0, y = 0, w = 30, h = 30; //some node has no geometry, this is the default
    if (geoRect) {
        x = geoRect[mxGraphMlConstants.X];
        y = geoRect[mxGraphMlConstants.Y];
        w = geoRect[mxGraphMlConstants.WIDTH];
        h = geoRect[mxGraphMlConstants.HEIGHT];
    } else {
        x = geoObj[mxGraphMlConstants.X_L] || x;
        y = geoObj[mxGraphMlConstants.Y_L] || y;
        w = geoObj[mxGraphMlConstants.WIDTH_L] || w;
        h = geoObj[mxGraphMlConstants.HEIGHT_L] || h;
    }

    var geo = node.geometry;
    geo.x = parseFloat(x) - dx;
    geo.y = parseFloat(y) - dy;
    geo.width = parseFloat(w);
    geo.height = parseFloat(h);
};

//TODO handle other ports cases
mxGraphMlCodec.prototype.importEdge = function (edgeElement, graph, parent, dx, dy) {
    var data = this.getDirectChildNamedElements(edgeElement, mxGraphMlConstants.DATA);
    var e;
    var id = edgeElement.getAttribute(mxGraphMlConstants.ID);
    var srcId = edgeElement.getAttribute(mxGraphMlConstants.EDGE_SOURCE);
    var trgId = edgeElement.getAttribute(mxGraphMlConstants.EDGE_TARGET);
    var srcPortId = edgeElement.getAttribute(mxGraphMlConstants.EDGE_SOURCE_PORT);
    var trgPortId = edgeElement.getAttribute(mxGraphMlConstants.EDGE_TARGET_PORT);

    var src = this.nodesMap[srcId];
    var trg = this.nodesMap[trgId];

    var edge = graph.insertEdge(parent, null, "", src.node, trg.node, "graphMLId=" + id);
    var style = {graphMlID: id};

    for (var i = 0; i < data.length; i++) {
        var d = data[i];
        var dataObj = this.dataElem2Obj(d);
        var desktopEdgeObj = dataObj["y:PolyLineEdge"] || dataObj["y:GenericEdge"] ||
            dataObj["y:ArcEdge"] || dataObj["y:BezierEdge"] ||
            dataObj["y:QuadCurveEdge"] || dataObj["y:SplineEdge"];

        if (dataObj.key == this.edgesKeys[mxGraphMlConstants.EDGE_GEOMETRY].key) {
            this.addEdgeGeo(edge, dataObj, dx, dy);
        } else if (dataObj.key == this.edgesKeys[mxGraphMlConstants.EDGE_STYLE].key) {
//			console.log(dataObj);
            this.addEdgeStyle(edge, dataObj, style);
        } else if (dataObj.key == this.edgesKeys[mxGraphMlConstants.EDGE_LABELS].key) {
            this.addLabels(edge, dataObj, style, graph);
        } else if (desktopEdgeObj) {
            this.addEdgeStyle(edge, dataObj, style);
            var absPoints = this.addEdgePath(edge, desktopEdgeObj["y:Path"], style, dx, dy);

            if (desktopEdgeObj["y:EdgeLabel"])
                this.addLabels(edge, desktopEdgeObj["y:EdgeLabel"], style, graph, absPoints);

            //special case for link edge
            //TODO link doesn't support arrow head types
            if (style["shape"] != null && style["shape"].indexOf("link") == 0) {
                style["width"] = style["strokeWidth"];
                style["strokeWidth"] = 1;
            }
        }
    }

    //handle simple ports (exit/entry[X/Y])
    if (src.ports && srcPortId) {
        var srcPort = src.ports[srcPortId];

        if (srcPort.pos) {
            style["exitX"] = srcPort.pos.x;
            style["exitY"] = srcPort.pos.y;
        }
    }

    if (trg.ports && trgPortId) {
        var trgPort = trg.ports[trgPortId];

        if (trgPort.pos) {
            style["entryX"] = trgPort.pos.x;
            style["entryY"] = trgPort.pos.y;
        }
    }

    edge.style = this.styleMap2Str(style);

    return edge;
};

mxGraphMlCodec.prototype.addEdgeGeo = function (edge, geoObj, dx, dy) {
    var list = geoObj[mxGraphMlConstants.Y_BEND];

    if (list) {
        var points = [];

        for (var i = 0; i < list.length; i++) {
            var pointStr = list[i][mxGraphMlConstants.LOCATION];

            if (pointStr) {
                var xy = pointStr.split(',');
                points.push(new mxPoint(parseFloat(xy[0]) - dx, parseFloat(xy[1]) - dy));
            }
        }

        edge.geometry.points = points;
    }
};

mxGraphMlCodec.prototype.addEdgePath = function (edge, pathObj, style, dx, dy) {
    var absPoints = [];
    if (pathObj) {
        var srcX = parseFloat(pathObj.sx), srcY = parseFloat(pathObj.sy),
            trgX = parseFloat(pathObj.tx), trgY = parseFloat(pathObj.ty);

        var srcGeo = edge.source.geometry;
        if (srcX != 0 || srcY != 0) {
            style["exitX"] = (srcX + srcGeo.width / 2) / srcGeo.width;
            style["exitY"] = (srcY + srcGeo.height / 2) / srcGeo.height;
            absPoints.push(new mxPoint(srcGeo.x + style["exitX"] * srcGeo.width - dx, srcGeo.y + style["exitY"] * srcGeo.height - dy));
        } else {
            absPoints.push(new mxPoint(srcGeo.x + srcGeo.width / 2 - dx, srcGeo.y + srcGeo.height / 2 - dy));
        }

        var endP = null;
        var trgGeo = edge.target.geometry;
        if (trgX != 0 || trgY != 0) {
            style["entryX"] = (trgX + trgGeo.width / 2) / trgGeo.width;
            style["entryY"] = (trgY + trgGeo.height / 2) / trgGeo.height;
            endP = new mxPoint(trgGeo.x + style["entryX"] * trgGeo.width - dx, trgGeo.y + style["entryY"] * trgGeo.height - dy);
        } else {
            endP = new mxPoint(trgGeo.x + trgGeo.width / 2 - dx, trgGeo.y + trgGeo.height / 2 - dy);
        }

        var list = pathObj["y:Point"];

        if (list) {
            if (!(list instanceof Array)) {
                list = [list];
            }

            var points = [];

            for (var i = 0; i < list.length; i++) {
                var p = new mxPoint(parseFloat(list[i].x) - dx, parseFloat(list[i].y) - dy)
                points.push(p);
                absPoints.push(p);
            }

            edge.geometry.points = points;
        }

        absPoints.push(endP);
    }
    return absPoints;
};

//TODO improve similarity handling
mxGraphMlCodec.prototype.addEdgeStyle = function (edge, styleObj, styleMap) {
    var dashStyleFn = function (val, map) {
        map["dashed"] = 1;
        //map["fixDash"] = 1;
        var pattern = null;
        switch (val) {
            case "DashDot":
                pattern = "3 1 1 1";
                break;
            case "Dot":
                pattern = "1 1";
                break;
            case "DashDotDot":
                pattern = "3 1 1 1 1 1";
                break;
            case "Dash":
                pattern = "3 1";
                break;
            default:
                pattern = val.replace(/0/g, '1');
        }

        if (pattern) {
            //Some patterns in graphML has only one number
            if (pattern.indexOf(" ") < 0) {
                pattern = pattern + " " + pattern;
            }
            map["dashPattern"] = pattern;
        }
    };

    var desktopLineStyleFn = function (val, map) {
        if (val != "line")
            map["dashed"] = 1;

        var pattern = null;

        switch (val) {
            case "dashed":
                pattern = "3 1";
                break;
            case "dotted":
                pattern = "1 1";
                break;
            case "dashed_dotted":
                pattern = "3 2 1 2";
                break;
        }

        if (pattern)
            map["dashPattern"] = pattern;
    };

    // can be mapping to WHITE => empty, BLACK => filled
    var endArrowFill = function (val, map) {
        map["endFill"] = (val == 'WHITE' || val.indexOf("white_") == 0 || val.indexOf("transparent_") == 0) ? "0" : "1";
    };
    // can be mapping to WHITE => empty, BLACK => filled
    var startArrowFill = function (val, map) {
        map["startFill"] = (val == 'WHITE' || val.indexOf("white_") == 0 || val.indexOf("transparent_") == 0) ? "0" : "1";
    };

    var startArrow = function (val, map) {
        map["startArrow"] = mxGraphMlArrowsMap[val] || "classic";
        startArrowFill(val, map);
    };

    var endArrow = function (val, map) {
        map["endArrow"] = mxGraphMlArrowsMap[val] || "classic";
        endArrowFill(val, map);
    }

    var desktopEdge = {
        "defaults":
            {
                "rounded": 0,
                "endArrow": "none"
            },
        "configuration": {key: "shape", mod: "shape"},
        "y:LineStyle": {
            "color": {key: "strokeColor", mod: "color"},
            "type": desktopLineStyleFn,
            "width": "strokeWidth"
        },
        "y:Arrows": {
            "source": startArrow,
            "target": endArrow
        },
        "y:BendStyle": {
            "smoothed": {key: "rounded", mod: "bool"}
        }
    };

    var arcEdgeStyle = mxUtils.clone(desktopEdge);
    arcEdgeStyle["defaults"]["curved"] = "1";

    this.mapObject(styleObj, {
        "yjs:PolylineEdgeStyle": {
            "defaults":
                {
                    "endArrow": "none",
                    "rounded": 0
                },
            "smoothingLength": function (val, map) {
                map["rounded"] = (val && parseFloat(val) > 0) ? "1" : "0";
            },
            "stroke": {key: "strokeColor", mod: "color"},
            "stroke.yjs:Stroke":
                {
                    "dashStyle": dashStyleFn,
                    "dashStyle.yjs:DashStyle.dashes": dashStyleFn,
                    "fill": {key: "strokeColor", mod: "color"},
                    "fill.yjs:SolidColorFill.color": {key: "strokeColor", mod: "color"},
                    //"lineCap": "", //??
                    "thickness.sys:Double": "strokeWidth",
                    "thickness": "strokeWidth"
                },
            "targetArrow": {key: "endArrow", mod: "arrow"},
            "targetArrow.yjs:Arrow": {
                "defaults":
                    {
                        "endArrow": "classic",
                        "endFill": "1",
                        "endSize": "6"
                    },
//				cropLength: "", //??
                "fill": endArrowFill,
                "scale": {key: "endSize", mod: "scale", scale: 5},
//				stroke: "", //?
                "type": {key: "endArrow", mod: "arrow"}
            },
            "sourceArrow": {key: "startArrow", mod: "arrow"},
            "sourceArrow.yjs:Arrow": {
                "defaults":
                    {
                        "startArrow": "classic",
                        "startFill": "1",
                        "startSize": "6"
                    },
//				cropLength: "", //??
                "fill": startArrowFill,
                "scale": {key: "startSize", mod: "scale", scale: 5},
//				stroke: "", //?
                "type": {key: "startArrow", mod: "arrow"}
            }
        },
        "y:PolyLineEdge": desktopEdge,
        "y:GenericEdge": desktopEdge,
        //We approximate all curved types with curve
        "y:ArcEdge": arcEdgeStyle,
        "y:BezierEdge": arcEdgeStyle,
        "y:QuadCurveEdge": arcEdgeStyle,
        "y:SplineEdge": arcEdgeStyle
    }, styleMap);
};

//TODO label offset
//TODO labels object is over swim lanes collapse button
mxGraphMlCodec.prototype.addLabels = function (node, lblObj, nodeStyle, graph, absPoints) {
    var lastChildIndex = node.getChildCount();
    var lblList = lblObj[mxGraphMlConstants.Y_LABEL] || lblObj;

    var lblTxts = [];
    var lblStyles = [];
    var lblLayouts = [];

    if (lblList) {
        if (!(lblList instanceof Array)) {
            lblList = [lblList];
        }

        for (var i = 0; i < lblList.length; i++) {
            var lbl = lblList[i];
//			console.log(lbl);
            var styleMap = {};
            var txt = lbl[mxGraphMlConstants.TEXT] || lbl;

            if (txt) txt = txt["#text"];

            //layout
            var layout = lbl[mxGraphMlConstants.LAYOUTPARAMETER] || lbl || {};

            //style
            var fontStyleFn = function (val, map) {
                if (val) {
                    val = val.toUpperCase();
                }

                var style = map["fontStyle"] || 0;

                switch (val) {
                    case "ITALIC":
                        style = style | 2;
                        break;
                    case "BOLD":
                        style = style | 1;
                        break;
                    case "UNDERLINE":
                        style = style | 4;
                        break;
                }
                map["fontStyle"] = style;
            };

            var underlineStyleFn = function (val, map) {
                var style = map["fontStyle"] || 0;

                if (val == "true") {
                    style = style | 4;
                }
                map["fontStyle"] = style;
            };

            this.mapObject(lbl, {
                "Style.yjs:DefaultLabelStyle":
                    {
                        "backgroundFill": {key: "labelBackgroundColor", mod: "color"},
                        "backgroundFill.yjs:SolidColorFill.color": {key: "labelBackgroundColor", mod: "color"},
                        "backgroundStroke": {key: "labelBorderColor", mod: "color"},
                        "backgroundStroke.yjs:Stroke.fill": {key: "labelBorderColor", mod: "color"},
                        "textFill": {key: "fontColor", mod: "color"},
                        "textFill.yjs:SolidColorFill.color": {key: "fontColor", mod: "color"},
                        "textSize": "fontSize",
                        "horizontalTextAlignment": "align",
                        "verticalTextAlignment": "verticalAlign",
                        "wrapping": function (val, map) {
                            //TODO mxGraph has a single type of wrapping only
                            if (val)
                                map["whiteSpace"] = "wrap";
                        },
                        "font.yjs:Font":
                            {
                                "fontFamily": "fontFamily",
                                "fontSize": "fontSize",
                                "fontStyle": fontStyleFn,
                                "fontWeight": fontStyleFn,
                                "textDecoration": fontStyleFn
                            }
                    },
                "Style.y:VoidLabelStyle": function (val, map) {
                    map["VoidLbl"] = true;
                },
                //Desktop format
                //hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" iconTextGap="4" modelName="custom" visible="true" width="4.0" x="26.277050018310547" y="70.76200103759766">
                "alignment": "align",
                //"autoSizePolicy": "",//??
                "fontFamily": "fontFamily",
                "fontSize": "fontSize",
                "fontStyle": fontStyleFn,
                "underlinedText": underlineStyleFn,
                "horizontalTextPosition": "",//?? how this is compared to alignment?
                "textColor": {key: "fontColor", mod: "color"},
                "verticalTextPosition": "verticalAlign",
                "hasText": {key: "hasText", mod: "bool"},
                "rotationAngle": "rotation"
            }, styleMap);

            if (!styleMap["VoidLbl"] && styleMap["hasText"] != "0") {
                lblTxts.push(txt);
                lblStyles.push(styleMap);
                lblLayouts.push(layout);
            }
        }
    }

    //TODO Use the style map with defaults to change the style
    for (var i = 0; i < lblTxts.length; i++) {
        if (lblTxts[i]) {
            if (lblLayouts[i] && lblLayouts[i]["bpmn:ParticipantParameter"])
                continue;

            lblTxts[i] = mxUtils.htmlEntities(lblTxts[i], false).replace(/\n/g, '<br/>');
            var geo = node.geometry;

            var lblCell = new mxCell(lblTxts[i], new mxGeometry(0, 0, geo.width, geo.height), 'text;html=1;spacing=0;' + this.styleMap2Str(lblStyles[i]));
            lblCell.vertex = true;
            node.insert(lblCell, lastChildIndex);
            var lGeo = lblCell.geometry;

//			console.log(lblTxts[i]);
//			console.log(lblLayouts[i]);

            if (lblLayouts[i]["y:RatioAnchoredLabelModelParameter"]) {
                var strSize = mxUtils.getSizeForString(lblTxts[i], lblStyles[i]["fontSize"], lblStyles[i]["fontFamily"]);
                var offsetStr = lblLayouts[i]["y:RatioAnchoredLabelModelParameter"]["LayoutOffset"];

                if (offsetStr) {
                    var parts = offsetStr.split(',');
                    lGeo.x = parseFloat(parts[0]);
                    lGeo.y = parseFloat(parts[1]);
                    lGeo.width = strSize.width;
                    lGeo.height = strSize.height;
                    lblCell.style += ";spacingTop=-4;";
                } else {
                    //TODO map there?
                    var lblRatio = lblLayouts[i]["y:RatioAnchoredLabelModelParameter"]["LabelRatio"];
                    var layoutRatio = lblLayouts[i]["y:RatioAnchoredLabelModelParameter"]["LayoutRatio"];

                    lblCell.style += ";align=center;";
                }
            } else if (lblLayouts[i]["y:InteriorLabelModel"]) //TODO this is probably can be done by setting the value?
            {
                //TODO merge with next one if they are identical in all cases!
                switch (lblLayouts[i]["y:InteriorLabelModel"]) {
                    case "Center":
                        lblCell.style += ";verticalAlign=middle;";
                        break;
                    case "North":
                        lGeo.height = 1;
                        break;
                    case "West":
                        lGeo.width = geo.height;
                        lGeo.height = geo.width;
                        //-90 rotation of origin
                        lGeo.y = geo.height / 2 - geo.width / 2;
                        lGeo.x = -lGeo.y;
                        lblCell.style += ";rotation=-90";
                        break;
                }
                lblCell.style += ";align=center;";
            }
            //TODO Spacing still need to be adjusted
            else if (lblLayouts[i]["y:StretchStripeLabelModel"] || lblLayouts[i]["y:StripeLabelModelParameter"]) {
                //TODO merge with previous one if they are identical in all cases!
                var dir = lblLayouts[i]["y:StretchStripeLabelModel"] || lblLayouts[i]["y:StripeLabelModelParameter"]["Position"];
                switch (dir) {
                    case "North":
                        lGeo.height = 1;
                        break;
                    case "West":
                        lGeo.width = geo.height;
                        lGeo.height = geo.width;
                        //-90 rotation of origin
                        lGeo.y = geo.height / 2 - geo.width / 2;
                        lGeo.x = -lGeo.y;
                        lblCell.style += ";rotation=-90;";
                        break;
                }
            } else if (lblLayouts[i]["bpmn:PoolHeaderLabelModel"]) {
                //TODO merge with previous one if they are identical in all cases!
                switch (lblLayouts[i]["bpmn:PoolHeaderLabelModel"]) {
                    case "NORTH":
                        lGeo.height = 1;
                        break;
                    case "WEST":
                        lGeo.width = geo.height;
                        lGeo.height = geo.width;
                        //-90 rotation of origin
                        lGeo.y = geo.height / 2 - geo.width / 2;
                        lGeo.x = -lGeo.y;
                        lblCell.style += ";rotation=-90;";
                        break;
                }
                lblCell.style += ";align=center;";
            } else if (lblLayouts[i]["y:InteriorStretchLabelModelParameter"]) {
                //TODO probably mapObject is needed in this method in general
                try {
                    var insets = lblLayouts[i]["y:InteriorStretchLabelModelParameter"]["Model"]["y:InteriorStretchLabelModel"]["Insets"];
                    //TODO how to map it?
                } catch (e) {
                    //Ignore
                }
                lblCell.style += ";align=center;";
            } else if (lblLayouts[i]["y:ExteriorLabelModel"]) {
                var lblPos;
                switch (lblLayouts[i]["y:ExteriorLabelModel"]) {
                    case "East":
                        lblCell.style += ";labelPosition=right;verticalLabelPosition=middle;align=left;verticalAlign=middle;";
                        break;
                    case "South":
                        lblCell.style += ";labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;";
                        break;
                    case "North":
                        lblCell.style += ";labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;"
                        break;
                    case "West":
                        lblCell.style += ";labelPosition=left;verticalLabelPosition=middle;align=right;verticalAlign=middle;";
                        break;
                }
            } else if (lblLayouts[i]["y:FreeEdgeLabelModelParameter"]) {
                lGeo.relative = true;
                lGeo.adjustIt = true;
                var layout = lblLayouts[i]["y:FreeEdgeLabelModelParameter"];
                var ratio = layout["Ratio"];
                var distance = layout["Distance"];
                var angle = layout["Angle"];

                //The geometry is adjusted after finishing the graph import (in decode method)
                if (ratio) {
                    lGeo.x = parseFloat(ratio);
                }

                if (distance) {
                    lGeo.y = parseFloat(distance);
                }

                if (angle) {
                    lblCell.style += ";rotation=" + (parseFloat(angle) * (180 / Math.PI));
                }

                lblCell.style += ";verticalAlign=middle;";
            } else if (lblLayouts[i]["y:EdgePathLabelModelParameter"]) {
                lGeo.relative = true;
                var layout = lblLayouts[i]["y:EdgePathLabelModelParameter"];
                var side = layout["SideOfEdge"];
                var ratio = layout["SegmentRatio"];

                lGeo.x = ratio ? (2 * parseFloat(ratio) - 1) : 0;

                //TODO this is an approximation, it needs to take into consideration the segment direction and label size
                if (side) {
                    switch (side) {
                        case "RightOfEdge":
                            lGeo.y = -15;
                            break;
                        case "LeftOfEdge":
                            lGeo.y = 15;
                            break;
                    }
                }

                lblCell.style += ";verticalAlign=middle;";

            } else //Desktop format or edge
            {
                var lblX = parseFloat(lblLayouts[i]["x"]),
                    lblY = parseFloat(lblLayouts[i]["y"]);

                if (lblLayouts[i]["width"]) lGeo.width = parseFloat(lblLayouts[i]["width"]);
                if (lblLayouts[i]["height"]) lGeo.height = parseFloat(lblLayouts[i]["height"]);

                if (node.edge) {
                    lGeo.relative = true;
                    lGeo.x = 0;
                    lGeo.y = 0;
                    var dx = node.source.geometry.getCenterX() - node.target.geometry.getCenterX();
                    var dy = node.source.geometry.getCenterY() - node.target.geometry.getCenterY();

                    if (graph && absPoints && lblLayouts[i]["y:ModelParameter"] && lblLayouts[i]["y:ModelParameter"]["y:SmartEdgeLabelModelParameter"]) {
                        var layout = lblLayouts[i]["y:ModelParameter"]["y:SmartEdgeLabelModelParameter"];
                        var angle = parseFloat(layout.angle), distance = parseFloat(layout.distance),
                            distanceToCenter = layout.distanceToCenter == "true",
                            position = layout.position, ratio = parseFloat(layout.ratio),
                            segment = parseFloat(layout.segment);

                        var eState = new mxCellState();
                        eState.absolutePoints = absPoints;
                        graph.view.updateEdgeBounds(eState);
                        var sign = (position == "left" ? 1 : -1);

                        if (segment == -1 && angle == 6.283185307179586) {
                            //TODO FIXME this case is still incorrect
                            lGeo.offset = new mxPoint(Math.abs(ratio) < 1 ? eState.segments[0] * ratio : ratio, sign * distance);
                        } else {
                            if (segment == -1) segment = 0;
                            //get the edge cell state to get the segments
                            var dist = 0;
                            for (var k = 0; k < segment; k++) {
                                dist += eState.segments[k];
                            }

                            dist += eState.segments[segment] * ratio;
                            lGeo.x = 2 * (dist / eState.length) - 1;
                            lGeo.y = ((position == "center" ? 0 : distance) + lGeo.height / 2 * sign * (Math.abs(dx) > Math.abs(dy) ? 1 : -1)) * sign;
                        }
                    } else if (!isNaN(lblX) && !isNaN(lblY)) {
                        lGeo.offset = new mxPoint(lblX + dx / 2 + (dx > 0 ? -lGeo.width : lGeo.width), lblY);
                    }
                } else {
                    lGeo.x = lblX || 0;
                    lGeo.y = lblY || 0;
                }
            }

            if (lblStyles[i]["rotation"]) {
                //TODO fix label coordinates after rotation
                //console.log(lGeo, lblStyles[i]["rotation"]);
                if (lblStyles[i]["rotation"] == 270) {
                    lGeo.x -= lGeo.height / 2;
                }
            }
        }
    }
    return {lblTxts: lblTxts, lblStyles: lblStyles};
};


mxGraphMlCodec.prototype.processPage = function (graph, pageIndex) {
    var codec = new mxCodec();
    var node = codec.encode(graph.getModel());
    node.setAttribute("style", "default-style2");
    var modelString = mxUtils.getXml(node);

    var output = "<diagram name=\"Page " + pageIndex + "\">";
    output += Graph.compress(modelString);
    output += "</diagram>";
    return output;

};

//These are the same as mxVsdxUtils functions, but added here to be self-dependent
/**
 * Returns a collection of direct child Elements that match the specified tag name
 * @param {*} parent the parent whose direct children will be processed
 * @param {string} name the child tag name to match
 * @return {*[]} a collection of matching Elements
 */
mxGraphMlCodec.prototype.getDirectChildNamedElements = function (parent, name) {
    var result = ([]);
    for (var child = parent.firstChild; child != null; child = child.nextSibling) {
        if ((child != null && (child.nodeType == 1)) && (name == child.nodeName)) {
            /* add */
            result.push(child);
        }
    }
    ;
    return result;
};

mxGraphMlCodec.prototype.getDirectFirstChildNamedElements = function (parent, name) {
    for (var child = parent.firstChild; child != null; child = child.nextSibling) {
        if ((child != null && (child.nodeType == 1)) && (name == child.nodeName)) {
            return child;
        }
    }
    ;
    return null;
};

/**
 * Returns a collection of direct child Elements
 * @param {*} parent the parent whose direct children will be processed
 * @return {*[]} a collection of all child Elements
 */
mxGraphMlCodec.prototype.getDirectChildElements = function (parent) {
    var result = ([]);
    for (var child = parent.firstChild; child != null; child = child.nextSibling) {
        if (child != null && (child.nodeType == 1)) {
            /* add */
            result.push(child);
        }
    }
    ;
    return result;
};
/**
 * Returns the first direct child Element
 * @param {*} parent the parent whose direct first child will be processed
 * @return {*} the first child Element
 */
mxGraphMlCodec.prototype.getDirectFirstChildElement = function (parent) {
    for (var child = parent.firstChild; child != null; child = child.nextSibling) {
        if (child != null && (child.nodeType == 1)) {
            return child;
        }
    }
    ;
    return null;
};

var mxGraphMlConverters = //TODO this code is taken from yworks.com. Is that OK?
    {
        "orgchartconverters.linebreakconverter": function (e, t) {
            if (typeof e === "string") {
                var i = e;
                while (i.length > 20 && i.indexOf(" ") > -1)
                    i = i.substring(0, i.lastIndexOf(" "));
                return t === "true" ? i : e.substring(i.length)
            }
            return ""
        },
        "orgchartconverters.borderconverter": function (e, t) {
            return typeof e === "boolean" ? e ? "#FFBB33" : "rgba(0,0,0,0)" : "#FFF"
        },
        "orgchartconverters.addhashconverter": function (e, t) {
            return typeof e === "string" ? typeof t === "string" ? "#" + e + t : "#" + e : e
        },
        "orgchartconverters.intermediateconverter": function (e, t) {
            return typeof e === "string" && e.length > 17 ? e.replace(/^(.)(\S*)(.*)/, "$1.$3") : e
        },
        "orgchartconverters.overviewconverter": function (e, t) {
            return typeof e === "string" && e.length > 0 ? e.replace(/^(.)(\S*)(.*)/, "$1.$3") : ""
        }
    };

var mxGraphMlArrowsMap =
    {
        "SIMPLE": "open",
        "TRIANGLE": "block",
        "DIAMOND": "diamond",
        "CIRCLE": "oval",
        "CROSS": "cross",
        "SHORT": "classicThin",
        "DEFAULT": "classic",
        "NONE": "none",
        //desktop
        "none": "none",
        "white_delta_bar": "block", //FIXME not a match
        "delta": "block",
        "standard": "classic",
        "diamond": "diamond",
        "white_diamond": "diamond",
        "white_delta": "block",
        "plain": "open",
        "skewed_dash": "dash",
        "concave": "openThin", //FIXME not exact match
        "transparent_circle": "oval",
        "crows_foot_many": "ERmany",
        "crows_foot_one": "ERone",
        "crows_foot_one_optional": "ERzeroToOne",
        "crows_foot_one_mandatory": "ERmandOne",
        "crows_foot_many_optional": "ERzeroToMany",
        "crows_foot_many_mandatory": "ERoneToMany",
        "white_circle": "oval",
        "t_shape": "ERone", //FIXME not a match
        "short": "classicThin",
        "convex": "", //FIXME not a match
        "cross": "cross"

    };

var mxGraphMlShapesMap =
    {
    "star5": "mxgraph.basic.star;flipV=1", //TODO This is not close enough!
    "star6": "mxgraph.basic.6_point_star",//;rotation=30", //TODO requires rotation!
    "star8": "mxgraph.basic.8_point_star",
    "sheared_rectangle": "parallelogram",
    "sheared_rectangle2": "parallelogram;flipH=1",
    "hexagon": "hexagon",
    "octagon": "mxgraph.basic.octagon",
    "ellipse": "ellipse",
    "round_rectangle": "rect;rounded=1;arcsize=30",
    "diamond": "rhombus",
    "fat_arrow": "step;perimeter=stepPerimeter",
    "fat_arrow2": "step;perimeter=stepPerimeter;flipH=1",
    "trapez": "trapezoid;perimeter=trapezoidPerimeter;flipV=1",
    "trapez2": "trapezoid;perimeter=trapezoidPerimeter",
    "triangle": "triangle",//;rotation=-90", //TODO requires rotation!
    "triangle2": "triangle",//;rotation=90", //TODO requires rotation!
    "rectangle": "rect",
    "rectangle3d": "", //TODO create this shape
    "roundrectangle": "rect;rounded=1;arcsize=30",
    "fatarrow": "step;perimeter=stepPerimeter",
    "fatarrow2": "step;perimeter=stepPerimeter;flipH=1",
    "parallelogram": "parallelogram",
    "parallelogram2": "parallelogram;flipH=1",
    "trapezoid2": "trapezoid;perimeter=trapezoidPerimeter;flipV=1",
    "trapezoid": "trapezoid;perimeter=trapezoidPerimeter",
    //Bevel TODO make them looks closer to yEd
    "bevelnode": "rect;glass=1;",
    "bevelnodewithshadow": "rect;glass=1;shadow=1",
    "bevelnode2": "rect;glass=1;rounded=1;arcsize=30",
    "bevelnode3": "rect;glass=1;rounded=1;arcsize=30;shadow=1",
    "shinyplatenode": "rect;glass=1",//;rotation=-90",//TODO requires rotation!
    "shinyplatenodewithshadow": "rect;glass=1;shadow=1",//;rotation=-90",//TODO requires rotation!
    "shinyplatenode2": "rect;glass=1;rounded=1;arcsize=30",//;rotation=-90",//TODO requires rotation!
    "shinyplatenode3": "rect;glass=1;rounded=1;arcsize=30;shadow=1",//;rotation=-90",//TODO requires rotation!
    //Table
//	"yed_table_node
    //flowchart
    "process": "mxgraph.flowchart.process",
    "decision": "mxgraph.flowchart.decision",
    "start1": "mxgraph.flowchart.start_1",
    "start2": "mxgraph.flowchart.start_2;aspect=fixed",
    "terminator": "mxgraph.flowchart.terminator",
    "cloud": "cloud",
    "data": "mxgraph.flowchart.data",
    "directdata": "mxgraph.flowchart.direct_data",
    "database": "mxgraph.flowchart.database",
    "document": "mxgraph.flowchart.document",
    "predefinedprocess": "mxgraph.flowchart.predefined_process",
    "storeddata": "mxgraph.flowchart.stored_data",
    "internalstorage": "mxgraph.flowchart.internal_storage",
    "sequentialdata": "mxgraph.flowchart.sequential_data;aspect=fixed",
    "manualinput": "mxgraph.flowchart.manual_input",
    "card": "card;size=10",
    "papertype": "mxgraph.flowchart.paper_tape",
    "delay": "mxgraph.flowchart.delay",
    "display": "mxgraph.flowchart.display",
    "manualoperation": "mxgraph.flowchart.manual_operation",
    "preparation": "mxgraph.flowchart.preparation",
    "looplimit": "mxgraph.flowchart.loop_limit",
    "looplimitend": "mxgraph.flowchart.loop_limit;flipV=1",
    "onpagereference": "mxgraph.flowchart.on-page_reference;aspect=fixed",
    "offpagereference": "mxgraph.flowchart.off-page_reference;aspect=fixed",
    "annotation": "mxgraph.flowchart.annotation_1", //TODO not similar!
    "usermessage": "mxgraph.arrows2.arrow;dy=0;dx=10;notch=0", //TODO requires rotation!
    "networkmessage": "mxgraph.arrows2.arrow;dy=0;dx=0;notch=10",
    //The same like above but with "com.yworks.flowchart." prefex. TODO should we just remove the prefex?
    "com.yworks.flowchart.start1": "mxgraph.flowchart.start_1",
    "com.yworks.flowchart.start2": "mxgraph.flowchart.start_2;aspect=fixed",
    "com.yworks.flowchart.terminator": "mxgraph.flowchart.terminator",
    "com.yworks.flowchart.process": "mxgraph.flowchart.process",
    "com.yworks.flowchart.predefinedprocess": "mxgraph.flowchart.predefined_process",
    "com.yworks.flowchart.decision": "mxgraph.flowchart.decision",
    "com.yworks.flowchart.looplimit": "mxgraph.flowchart.loop_limit",
    "com.yworks.flowchart.looplimitend": "mxgraph.flowchart.loop_limit;flipV=1",
    "com.yworks.flowchart.document": "mxgraph.flowchart.document",
    "com.yworks.flowchart.data": "mxgraph.flowchart.data",
    "com.yworks.flowchart.directdata": "mxgraph.flowchart.direct_data",
    "com.yworks.flowchart.storeddata": "mxgraph.flowchart.stored_data",
    "com.yworks.flowchart.sequentialdata": "mxgraph.flowchart.sequential_data;aspect=fixed",
    "com.yworks.flowchart.database": "mxgraph.flowchart.database",
    "com.yworks.flowchart.internalstorage": "mxgraph.flowchart.internal_storage",
    "com.yworks.flowchart.manualinput": "mxgraph.flowchart.manual_input",
    "com.yworks.flowchart.card": "card;size=10",
    "com.yworks.flowchart.papertype": "mxgraph.flowchart.paper_tape",
    "com.yworks.flowchart.cloud": "cloud",
    "com.yworks.flowchart.delay": "mxgraph.flowchart.delay",
    "com.yworks.flowchart.display": "mxgraph.flowchart.display",
    "com.yworks.flowchart.manualoperation": "mxgraph.flowchart.manual_operation",
    "com.yworks.flowchart.preparation": "mxgraph.flowchart.preparation",
    "com.yworks.flowchart.onpagereference": "mxgraph.flowchart.on-page_reference;aspect=fixed",
    "com.yworks.flowchart.offpagereference": "mxgraph.flowchart.off-page_reference;aspect=fixed",
    "com.yworks.flowchart.usermessage": "mxgraph.arrows2.arrow;dy=0;dx=10;notch=0", //TODO requires rotation!
    "com.yworks.flowchart.networkmessage": "mxgraph.arrows2.arrow;dy=0;dx=0;notch=10",
    "com.yworks.flowchart.annotation": "mxgraph.flowchart.annotation_1", //TODO not similar!

    //icons (network)
    "database.svg": "mxgraph.networks.storage", //TODO not similar!
    "laptop.svg": "mxgraph.networks.laptop",//TODO not similar!
    "server.svg": "mxgraph.networks.server",//TODO not similar!
    "smartphone.svg": "mxgraph.networks.mobile",//TODO not similar! //TODO fixed aspect ratio
    "switch.svg": "mxgraph.networks.switch",//TODO not similar! //TODO fixed aspect ratio
    "wlan.svg": "mxgraph.networks.wireless_hub",//TODO not similar!
    "workstation.svg": "mxgraph.networks.pc",//TODO not similar!
    //bpmn
    "transaction": "ext;double=1;rounded=1",
    "sub_process": "ext;rounded=1",
    "call_activity": "ext;rounded=1;strokeWidth=3",
    //TODO two colors for stroke!
    "exclusive_with_marker": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=none;symbol=exclusiveGw",
    "event_based": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=boundInt;symbol=multiple",
    "parallel": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=none;symbol=parallelGw",
    "inclusive": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=end;symbol=general",
    "complex": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=none;symbol=complexGw",
    "exclusive_event_based": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=standard;symbol=multiple",
    "parallel_event_based": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;outline=standard;symbol=parallelMultiple",
    //hexagon
    "calling_global_conversation": "hexagon;strokeWidth=4",
    //mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=general
    "message": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=message",
    "timer": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=timer",
    "escalation": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=escalation",
    "conditional": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=conditional",
    "link": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=link",
    "error": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=error",
    "cancel": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=cancel",
    "compensation": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=compensation",
    "signal": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=signal",
    "multiple": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=multiple",
    "parallel_multiple": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=parallelMultiple",
    "terminate": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;symbol=terminate",

//	"com.yworks.bpmn.pool
    "com.yworks.bpmn.activity.withshadow": "js:bpmnActivityShadow",
    "com.yworks.bpmn.activity": "js:bpmnActivity",
    "com.yworks.bpmn.gateway.withshadow": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway;shadow=1",
    "com.yworks.bpmn.gateway": "mxgraph.bpmn.shape;perimeter=rhombusPerimeter;background=gateway",
    "com.yworks.bpmn.event.withshadow": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter;shadow=1",
    "com.yworks.bpmn.event": "mxgraph.bpmn.shape;perimeter=ellipsePerimeter",
    "com.yworks.bpmn.conversation.withshadow": "hexagon;shadow=1",
    "com.yworks.bpmn.conversation": "hexagon",
    "com.yworks.bpmn.artifact.withshadow": "js:bpmnArtifactShadow",
    "com.yworks.bpmn.artifact": "js:bpmnArtifact",

    bpmnOutline: {
        "sub_process_interrupting": "eventInt",
        "sub_process_non_interrupting": "eventNonint",
        "catching": "catching",
        "boundary_interrupting": "boundInt",
        "boundary_non_interrupting": "boundNonint",
        "throwing": "throwing",
        "end": "end",
        "event_characteristic_start": "standard",
        "event_characteristic_end": "end",
        "event_characteristic_intermediate_catching": "catching",
        "event_characteristic_start_event_sub_process_interrupting": "eventInt",
        "event_characteristic_intermediate_boundary_interrupting": "boundInt"
    },
    bpmnSymbol: {
        "event_type_plain": "general",
        "event_type_message": "message",
        "event_type_timer": "timer",
        "event_type_escalation": "escalation",
        "event_type_conditional": "conditional",
        "event_type_link": "link",
        "event_type_error": "error",
        "event_type_cancel": "cancel",
        "event_type_compensation": "compensation",
        "event_type_signal": "signal",
        "event_type_multiple": "multiple",
        "event_type_parallel_multiple": "parallelMultiple",
        "event_type_terminate": "terminate",
        "gateway_type_plain": "",
        "gateway_type_data_based_exclusive": "exclusiveGw",
        "gateway_type_inclusive": "general;outline=end",
        "gateway_type_parallel": "parallelGw",
        "gateway_type_complex": "complexGw",
        "gateway_type_event_based_exclusive": "multiple;outline=catching",
        "gateway_type_event_based_exclusive_start_process": "multiple;outline=standard",
        "gateway_type_parallel_event_based_exclusive_start_process": "parallelMultiple;outline=standard",
        "conversation_type": "",
        "artifact_type_data_object": "js:bpmnDataObject",
        "artifact_type_annotation": "mxgraph.flowchart.annotation_1",
        "artifact_type_group": "rect;fillColor=none;dashed=1;dashPattern=3 1 1 1;collapsible=0;rounded=1",
        "artifact_type_data_store": "datastore",
        "artifact_type_reply_message": "message;strokeColor=#000000;fillColor=#A1A1A1",
        "artifact_type_request_message": "message",
        "connection_type_sequence_flow": "",
        "connection_type_default_flow": "",
        "connection_type_conditional_flow": "",
        "connection_type_association": "",
        "connection_type_directed_association": "",
        "connection_type_bidirected_association": "",
        "connection_type_message_flow": "",
        "connection_type_conversation_link": "",
        "connection_type_forked_conversation_link": "",
        "pool_type_lane_and_column": "",
        "pool_type_empty": "",
        "pool_type_lane": "",
        "pool_type_column": "",
        "activity_type": ""
    },
    //desktop entity relationship
    "com.yworks.entityrelationship.big_entity": "js:relationship_big_entity",
    "com.yworks.entityrelationship.small_entity": "ext",
    "com.yworks.entityrelationship.relationship": "rhombus",
    "com.yworks.entityrelationship.attribute": "js:relationship_attribute",
    //SBGN
    "com.yworks.sbgn.unspecifiedentity": "ellipse",
    "com.yworks.sbgn.simplechemical": "ellipse",
    "com.yworks.sbgn.macromolecule": "ext;rounded=1",
    "com.yworks.sbgn.nucleicacidfeature": "", //TODO create this shape!
    "com.yworks.sbgn.perturbingagent": "", //TODO create this shape!
    "com.yworks.sbgn.phenotype": "hexagon;perimeter=hexagonPerimeter2;size=0.2",
    "com.yworks.sbgn.emptyset": "lineEllipse;line=vertical;perimeter=ellipsePerimeter",//;rotation=45", //TODO create this shape!
    "com.yworks.sbgn.submap": "",  //TODO create this shape!
    "com.yworks.sbgn.unitofinformation": "",  //TODO create this shape!
    "com.yworks.sbgn.statevariable": "mxgraph.flowchart.terminator",
    "com.yworks.sbgn.tag": "offPageConnector;size=0.25", //;rotation=90", //TODO create this shape without rotation!
    "com.yworks.sbgn.process": "rect",
    "com.yworks.sbgn.operator": "ellipse",

    //special edges
    "com.yworks.edge.framed": "link",

    //Male/Female icons (FIXME Not similar and unsafe as it refers to remote resources)
    "usericon_female1.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-128.png",
    "usericon_female2.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-128.png",
    "usericon_female3.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-128.png",
    "usericon_female4.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-128.png",
    "usericon_female5.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-128.png",
    "usericon_male1.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-128.png",
    "usericon_male2.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-128.png",
    "usericon_male3.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-128.png",
    "usericon_male4.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-128.png",
    "usericon_male5.svg": "image;image=https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-128.png"
};

var mxGraphMlConstants =
    {
        ID: "id",

        KEY_FOR: "for",

        KEY_NAME: "attr.name",

        KEY_TYPE: "attr.type",

        KEY_YTYPE: "yfiles.type",

        GRAPH: "graph",

        GRAPHML: "graphml",

        NODE: "node",

        EDGE: "edge",

        HYPEREDGE: "hyperedge",

        PORT: "port",

        ENDPOINT: "endpoint",

        KEY: "key",

        DATA: "data",

        ALL: "all",

        EDGE_SOURCE: "source",

        EDGE_SOURCE_PORT: "sourceport",

        EDGE_TARGET: "target",

        EDGE_TARGET_PORT: "targetport",

        EDGE_DIRECTED: "directed",

        EDGE_UNDIRECTED: "undirected",

        EDGE_DEFAULT: "edgedefault",

        PORT_NAME: "name",

        HEIGHT: "Height",

        WIDTH: "Width",

        X: "X",

        Y: "Y",

        HEIGHT_L: "height",

        WIDTH_L: "width",

        X_L: "x",

        Y_L: "y",

        JGRAPH: "jGraph:",

        GEOMETRY: "y:Geometry",

        FILL: "Fill",

        SHAPENODE: "y:ShapeNode",

        SHAPEEDGE: "ShapeEdge",

        JGRAPH_URL: "http://www.jgraph.com/",

        KEY_NODE_ID: "d0",

        KEY_NODE_NAME: "nodeData",

        KEY_EDGE_ID: "d1",

        KEY_EDGE_NAME: "edgeData",

        STYLE: "Style",

        SHAPE: "Shape",

        TYPE: "type",

        LABEL: "label",

        TEXT: "text",

        PROPERTIES: "properties",

        SOURCETARGET: "SourceTarget",

        RECT: "y:RectD",

        NODE_LABELS: "NodeLabels",

        NODE_LABEL: "y:NodeLabel",

        NODE_GEOMETRY: "NodeGeometry",

        USER_TAGS: "UserTags",

        NODE_STYLE: "NodeStyle",

        NODE_GRAPHICS: "nodegraphics",

        NODE_VIEW_STATE: "NodeViewState",

        EDGE_LABELS: "EdgeLabels",

        EDGE_GEOMETRY: "EdgeGeometry",

        EDGE_STYLE: "EdgeStyle",

        EDGE_VIEW_STATE: "EdgeViewState",

        PORT_LOCATION_PARAMETER: "PortLocationParameter",

        PORT_STYLE: "PortStyle",

        PORT_VIEW_STATE: "PortViewState",

        SHARED_DATA: "SharedData",

        Y_SHARED_DATA: "y:SharedData",

        X_KEY: "x:Key",

        GRAPHML_REFERENCE: "y:GraphMLReference",

        RESOURCE_KEY: "ResourceKey",

        Y_RESOURCES: "y:Resources",

        Y_RESOURCE: "y:Resource",

        REFID: "refid",

        X_LIST: "x:List",

        X_STATIC: "x:Static",

        Y_BEND: "y:Bend",

        LOCATION: "Location",

        Y_LABEL: "y:Label",

        LAYOUTPARAMETER: "LayoutParameter",

        YJS_DEFAULTLABELSTYLE: "yjs:DefaultLabelStyle",

        MEMBER: "Member"
    };


EditorUi.prototype.doImportGraphML = function (xmlData, done, onerror) {
    new mxGraphMlCodec().decode(xmlData, done, onerror);
};

